0
点赞
收藏
分享

微信扫一扫

JavaEE初阶 -多线程基础篇 (线程池)

树下的老石头 2022-04-02 阅读 63

为什么要使用线程池?
Java标准库中的线程池
实现一个线程池


1. 为什么要使用线程池?

  提前将线程创建好, 存在线程池中, 后面如果要使用线程可以直接从线程池中获取, 而不必从系统中申请, 线程结束之后也不是由系统销毁, 而是继续存入线程池中, 这样, 线程创建和销毁的速度会加快.

那么, 为什么在线程池里取出线程比系统创建线程要快呢?

  操作系统中存在两种状态, 用户态内核态, 程序中的部分指令需要调用操作系统的API, 进一步的逻辑都会在内核中执行, 这种代码就是内核态的代码.

  创建线程本身就需要内核的支持(创建线程就是在内核中创建一个PCB, 并将PCB串在链表上), 包括开启线程(Thread.start)也需要进入内核态来进行.

  而将创建好的线程加入线程池中, 由于线程池是在用户态下实现的, 在线程池中存放/取出线程, 这个过程不需要涉及到内核态.

  一般认为, 纯用户态的操作, 效率高于经过内核态处理的操作. 纯用户态的操作是可控的, 而内核态的操作整体是不可控的, 我们这里提到的内核态操作的效率更低, 指的是内核态的执行过程是不可控的, 内核态有可能在执行这条指令的时候也执行了其他的指令, 所以说纯用户态的操作效率高于经过内核态的操作.

2. Java标准库中的线程池

  Java中描述线程池的类叫做"ThreadPoolExecutor", 这个类位于java.util.concurrent包中, 共有四种构造方法:

看起来有点难理解, 我们只需要关注第四个构造方法中的参数即可:

线程池的参数非常多, 其中最重要的是线程的个数问题.

有一个程序要并发地去执行一些任务, 如果使用线程的话, 如何设置线程池的大小?

  通过性能测试找到一个合适的值, 例如:写一个服务器程序, 服务器里通过线程池, 多线程的处理用户请求, 就可以对这个服务器进行性能测试, 比如构造一些请求, 发送给服务器, 要测试性能, 这里的请求就要构造很多, 然后根据实际的业务场景, 构造合适的请求个数

  根据不同的线程数, 来观察持有的CPU的占用率, 从而找到一个速度能被用户接受, CPU占用率也合理的平衡点.

  (对于线上服务器来说, 为了应对一些突发情况, 需要CPU留有一定的冗余, 因此CPU的占用率不能过高)

标准库中还提供了一个简化版的线程池 - Executors, 本质是对ThreadPoolExecutor进行封装, 提供一些默认参数.

public static void main(String[] args) {
    //创建一个固定大小的线程池, 参数决定了线程池中线程的最大个数
    ExecutorService pool = Executors.newFixedThreadPool(10);
    //创建一个自动扩容的线程池
    Executors.newCachedThreadPool();
    //创建一个只有一个线程的线程池
    Executors.newSingleThreadExecutor();
    //创建一个具有定时器功能的线程池
    Executors.newScheduledThreadPool(3000);
    pool.submit(new Runnable() {
        @Override
        public void run() {
           //执行的任务
        }
    });
}

3. 实现一个线程池

实现线程池需要:

  1. 描述任务(使用Runnable接口)
  2. 组织任务(使用BlockingQueue阻塞队列)
  3. 描述工作线程
  4. 组织线程
  5. 创建一个方法, 允许线程入池

具体实现代码:

class MyThreadPool{
    //1. 使用Runnable描述任务
    //2. 使用阻塞队列组织任务
    private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>();
    //3. 描述一个线程
    static class Worker extends Thread{
        private BlockingQueue<Runnable> blockingQueue = null;
        public Worker(BlockingQueue<Runnable> blockingQueue){
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            while (true){
                try {
                    //循环地获取任务队列中的任务
                    //如果队列不为空, 获取到队首元素, 队列为空则产生阻塞
                    Runnable runnable = blockingQueue.take();
                    //执行任务
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //4. 组织若干个线程
    private List<Thread> workers = new ArrayList<>();

    //在构造方法中创建若干个线程放到上面的顺序表中, n代表线程的个数
    public MyThreadPool(int n) {
        for(int i=0;i<n;++i){
            Worker worker = new Worker(blockingQueue);
            worker.start();
            workers.add(worker);
        }
    }
    //5. 创建一个方法, 允许线程进入线程池
    public void submit(Runnable runnable){
        try {
            blockingQueue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The end

举报

相关推荐

0 条评论