Java 线程的一点理解

221 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

线程做了什么

Java编译器在编译的时候都认为传递给他的是一个对象,然后执行对象的run方法。刚刚我们使用的Thread的构造函数如下:

 public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }

Thread 在拿到这个对象的时候,当我们执行 Thread 的 start 方法的时候,会执行到一个 native 方法 start0

当JVM执行到这个方法的时候会调用操作系统给上层提供的API创建一个线程,然后这个线程会去解释执行我们之前给Thread对象传入的对象的run方法字节码,当run方法字节码执行完成之后,这个线程就会退出。

JVM给我们提供的线程就是去完成一个函数,然后退出

事实上JVM在使用操作系统给他提供的线程的时候也是给这个线程传递一个函数地址,然后让这个线程执行完这个函数。只不过JVM给操作系统传递的函数,这个函数的功能就是去解释执行字节码,当解释执行字节码完成之后,这个函数也会退出(被系统回收)。

为什么需要线程池

创建线程是需要系统资源的,比如说内存,因为操作系统是系统资源的管理者,因此一般需要系统资源的方法都需要操作系统的参与,因此创建线程需要操作系统的帮忙,而一旦需要操作系统介入,执行代码的状态就需要从用户态到内核态转换(内核态能够执行许多用户态不能够执行的指令),当操作系统创建完线程之后有需要返回用户态,我们的代码将继续被执行。

从用户态到内核态再回到内核态,需要两次的上下文切换,同时还需要执行一些操作系统的函数。如果在并发非常高的情况,我们频繁的去生成线程然后销毁,过程非常耗时间

线程池实现原理

  • 线程就是执行一个函数。
  • 线程池当中的线程可以执行很多函数,但是不会退出。

通过 while 循环可以实现乞丐版线程池,在 while 循环中不断从任务队列当中获取任务函数,然后执行,直到要求停止线程池当中的线程的时候线程再进行退出。例子:

  public void run() {
    while (!isStopped) {
      try {
        Runnable task = tasks.take();
        task.run();
      } catch (InterruptedException e) {
        // do nothing
      }
    }
  }

简易版线程池实现

实现思路

在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务然后执行,现在需要思考我们应该用什么去实现任务池呢?

答案是阻塞队列,因为我们需要保证在多个线程往任务池里面加入任务的时候并发安全,JDK已经给我们提供了这样的数据结构——BlockingQueue,这个是一个并发安全的阻塞队列,他之所以叫做阻塞队列,是因为我们可以设置队列当中可以容纳数据的个数,当加入到队列当中的数据超过这个值的时候,试图将数据加入到阻塞队列当中的线程就会被挂起。当队列当中为空的时候,试图从队列当中取出数据的线程也会被挂起。

线程的设计

在我们自己实现的线程池当中我们定一个Worker类去不断的从任务池当中取出任务,然后进行执行。Work类中有两个主要参数

isStopped:线程是否停止工作

thisThread:保存当前是哪个线程在执行任务

public class Worker implements Runnable{
    // 表示正在执行任务的线程
    private Thread thisThread;
    // 由线程池传递过来的任务队列
    private BlockingQueue<Runnable> taskQueue;
    // 表示 worker 是否停止工作 需要使用 volatile 保证线程之间的可见性
    private volatile boolean isStopped;
​
    // 这个构造方法是在线程池的实现当中会被调用
    public Worker(BlockingQueue<Runnable> taskQueue) {
        this.taskQueue = taskQueue;
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 线程执行的函数
     * @Date: 9:42 AM 2022/10/31
     **/
    @Override
    public void run() {
        // 获取执行任务的线程
        thisThread = Thread.currentThread();
        // 当线程没有停止的时候就不断的去任务池当中取出任务
        while (!isStopped){
            try {
                // 从任务池当中取出任务 当没有任务的时候线程会被这个方法阻塞
                Runnable take = taskQueue.take();
                // 执行任务 任务就是一个 Runnable 对象
                take.run();
            } catch (InterruptedException e) {
                //do nothing
​
                //当任务池当中没有任务的时候,线程会被阻塞在 take 方法上
                //如果后面没有任务提交,那它就会一直阻塞,如何唤醒
                //答案:调用线程的 interruput 方法,那么 take 方法就会产生一个异常,然后我们捕获到一个异常,然后线程退出
            }
        }
    }
​
    public synchronized void stopWorker(){
        if (isStopped){
            throw new RuntimeException("thread has been interrupted");
        }
        isStopped = true;
        //中断线程产生异常
        thisThread.interrupt();
    }
​
    public synchronized boolean isStopped(){
        return isStopped;
    }
​
}

线程池实现代码

我们大致设计的线程池的初步结构:

ThreadPool对象:产生指定线程的数目线程并且启动他们去执行任务,

shutDown :在关闭线程的时候还需要保证所有的任务都被执行完成然后才关闭所有的线程,再退出,

stop:强制退出,不用执行所有的任务了,就直接退出

public class ThreadPool {
​
    // 任务池
    private BlockingQueue taskQueue;
    private volatile boolean isStopped;
    // 保存所所有的执行任务的线程
    private final List<Worker> workers = new ArrayList<>();
​
    public ThreadPool(int numThreads,int maxTasks){
        this.taskQueue = new ArrayBlockingQueue(maxTasks);
        for (int i = 0;i<numThreads;i++){
            workers.add(new Worker(this.taskQueue));
        }
        int i = 1;
        //产生线程,然后启动线程
        for (Worker worker:workers){
            new Thread(worker,"ThreadPool-" + i + "-thread").start();
            i++;
        }
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 向线程池提交任务
     * @Date: 10:20 AM 2022/10/31
     **/
    public void execute(Runnable runnable) throws InterruptedException {
        if (isStopped){
            //如果线程池已经停下来了,就不再向任务队列当中提交任务
            log.info("thread pool has been stopped, so quit submitting task");
            return;
        }
        taskQueue.put(runnable);
    }
    /**
     * @Author: Take-off
     * @Description: //TODO 强制关闭
     * @Date: 10:21 AM 2022/10/31
     **/
    public synchronized void stop(){
        isStopped = true;
        for (Worker worker:workers){
            worker.stopWorker();
        }
    }
​
    public synchronized void shutDown(){
        isStopped = true;
        waitForAllTasks();
        stop();
    }
​
    private void waitForAllTasks(){
        while (taskQueue.size() > 0){
            Thread.yield();
        }
    }
}

线程提交任务的流程

线程提交任务流程.jpeg

功能设计

能够提交Runnable的任务和Callable的任务

线程池能够自己实现动态的扩容和所容,动态调整线程池当中线程的数目,当任务多的时候能够增加线程的数目,当任务少的时候多出来的线程能够自动退出。

有自己的拒绝策略,当任务队列满了,线程数也达到最大的时候,需要拒绝提交的任务。

参数解释

corePoolSize:这个参数你可以理解为线程池当中至少需要 corePoolSize 个线程,初始时线程池当中线程的个数为0,当线程池当中线程的个数小于 corePoolSize 每次提交一个任务都会创建一个线程,并且先执行这个提交的任务,然后再去任务队列里面去获取新的任务,然后再执行。

maximumPoolSize:这个参数指的是线程池当中能够允许的最大的线程的数目,当任务队列满了之后如果这个时候有新的任务想要加入队列当中,当发现队列满了之后就创建新的线程去执行任务,但是需要满足最大的线程的个数不能够超过 maximumPoolSize 。

keepAliveTime 和 unit:这个主要是用于时间的表示,当队列当中多长时间没有数据的时候线程自己退出,前面谈到了线程池当中任务过多的时候会超过 corePoolSize ,当线程池闲下来的时候这些多余的线程就可以退出了。

taskQueue:这个就是用于保存任务的阻塞队列,用不保存提交的任务

threadFactory:这个参数倒不是很重要,线程工厂。

policy:拒绝策略,主要有一下四种策略:

  • AbortPolicy:抛出异常。
  • DiscardPolicy:放弃这个任务。
  • CallerRunPolicy:提交任务的线程执行。
  • DiscardOldestPolicy:放弃等待时间最长的任务。

workers:用于保存工作线程。

isStopped:线程池是否被关闭了。

useTimed:主要是用于表示是否使用上面的 keepAliveTime 和 unit,如果使用就是在一定的时间内,如果没有从任务队列当中获取到任务,线程就从线程池退出,但是需要保证线程池当中最小的线程个数不小于 corePoolSize 。

检查线程池状态

检查线程池的状态,当线程池被停下来之后就不能够在提交任务:

    private void checkPoolState(){
        if (isStopped){
            //线程池停下来就不向任务队列提交任务
            throw new RuntimeException("thread pool has been stopped, so quit submitting task");
        }
    }

往线程池当中提交任务并且产生一个线程,并且这个线程执行的第一个任务就是传递的参数。max表示线程的最大数目,max == true 的时候表示使用 maximumPoolSize 否则使用 corePoolSize,当返回值等于 true 的时候表示执行成功,否则表示执行失败。

public synchronized boolean addWorker(Runnable runnable,boolean max){
        if (ct.get() >=    corePoolSize && !max){
            return false;
        }
        if (ct.get() >= maximumPoolSize && max){
            return false;
        }
        Worker worker = new Worker(runnable);
        workers.add(worker);
        Thread thread = new Thread(worker, "ThreadPool-" + "Thread-" + ct.addAndGet(1));
        thread.start();
        return true;
    }

实现Callable

只需要将传入的Callable对象封装成一个FutureTask对象即可,因为FutureTask实现了Callable和Runnable两个接口,然后将这个结果返回即可,得到这个对象,再调用对象的 get 方法就能够得到结果。

public <V> RunnableFuture<V> submit(Callable<V> task) throws InterruptedException {
        checkPoolState();
        FutureTask<V> futureTask = new FutureTask<>(task);
        execute(futureTask);
        return futureTask;
    }

拒绝策略

private void reject(Runnable runnable) throws InternalException {
        switch (policy){
            case ABORT:throw new RuntimeException("task queue is full");
            case CALLER_RUN:runnable.run();break;
            case DISCARD_OLDEST:
                // 直接放弃这个任务
                return;
            case DISCARD:
                // 放弃等待时间最长的任务,也就是队列中的第一个任务
                taskQueue.poll();
                // 重新执行这个任务
                execute(runnable);
            default:break;
        }
    }

枚举类

public enum RejectPolicy {
    ABORT,
    CALLER_RUN,
    DISCARD_OLDEST,
    DISCARD
}

线程池关闭

一共两种方式实现线程池关闭:

  • 直接关闭线程池,不管任务队列当中的任务是否被全部执行完成。
  • 安全关闭线程池,先等待任务队列当中所有的任务被执行完成,再关闭线程池,但是在这个过程当中不允许继续提交任务了,这一点已经在函数 checkPoolState 当中实现了。
 /**
     * @Author: Take-off
     * @Description: //TODO 强制关闭线程池
     * @Date: 4:41 PM 2022/10/31
     **/
    public synchronized void stop(){
        isStopped  = true;
        for (Worker worker:workers){
            worker.stopWorker();
        }
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 正常关闭线程池
     * @Date: 4:46 PM 2022/10/31
     **/
    public synchronized void shutDown(){
        isStopped = true;
        waitForAllTasks();
        stop();
    }
​
    private void waitForAllTasks(){
        // 当线程池还有任务时,不退出循环
        while (taskQueue.size() > 0){
            Thread.yield();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

工作线程Runnable

class Worker implements Runnable {
        private Thread thisThread;
        private final Runnable firstTask;
        private volatile boolean isStopped;
        public Worker(Runnable firstTask){
            this.firstTask = firstTask;
        }
​
        @Override
        public void run() {
            firstTask.run();
            thisThread = Thread.currentThread();
            while (!isStopped){
                try {
                    Runnable task = useTimed ? taskQueue.poll(keepAliveTime, unit) : taskQueue.take();
                    if (task == null){
                        int i;
                        boolean exit = true;
                        if (ct.get() > corePoolSize){
                            do {
                                i = ct.get();
                                if (i <= corePoolSize){
                                    exit = false;
                                    break;
                                }
                            }while (!ct.compareAndSet(i,i-1));
                            if (exit){
                                return;
                            }
                        }
                    }else {
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
​
        public synchronized void stopWorker(){
            if (isStopped){
                throw new RuntimeException("thread has been interrupted");
            }
            isStopped = true;
            thisThread.interrupt();
        }
    }

总体实现

public class ThreadPool {
    //当前在执行任务的线程个数
    private AtomicInteger ct = new AtomicInteger(0);
​
    private int corePoolSize;
    private int maximumPoolSize;
    private long keepAliveTime;
    private TimeUnit unit;
    private BlockingQueue<Runnable> taskQueue;
    private RejectPolicy policy;
​
    private List<Worker> workers =  new ArrayList<>();
​
    private volatile boolean isStopped;
    private boolean useTimed;
​
    public int getCt(){
        return ct.get();
    }
​
    public ThreadPool(int corePoolSize, int maximumPoolSize, TimeUnit unit, long keepAliveTime, RejectPolicy policy, int maxTasks) {
​
        assert corePoolSize > 0;
        assert maximumPoolSize > 0;
        assert keepAliveTime >= 0;
        assert maxTasks > 0;
​
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.unit = unit;
        this.policy = policy;
        this.keepAliveTime = keepAliveTime;
        taskQueue = new ArrayBlockingQueue<Runnable>(maxTasks);
        useTimed = keepAliveTime != 0;
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 向线程池提交任务
     * @Date: 4:34 PM 2022/10/31
     **/
    public void execute(Runnable runnable){
        checkPoolState();
​
        if (addWorker(runnable,false)
                || !taskQueue.offer(runnable)
                || addWorker(runnable,true)){
            return;
        }
​
        //任务队列满了就拒绝这个任务
        if (!taskQueue.offer(runnable)){
            reject(runnable);
        }
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 检查线程池的状态,当线程池被停下来之后就不能够在提交任务
     * @Date: 4:34 PM 2022/10/31
     **/
    private void checkPoolState(){
        if (isStopped){
            //线程池停下来就不向任务队列提交任务
            throw new RuntimeException("thread pool has been stopped, so quit submitting task");
        }
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 往线程池当中提交任务并且产生一个线程
     * @Date: 4:34 PM 2022/10/31
     **/
    public synchronized boolean addWorker(Runnable runnable,boolean max){
        if (ct.get() >=    corePoolSize && !max){
            return false;
        }
        if (ct.get() >= maximumPoolSize && max){
            return false;
        }
        Worker worker = new Worker(runnable);
        workers.add(worker);
        Thread thread = new Thread(worker, "ThreadPool-" + "Thread-" + ct.addAndGet(1));
        thread.start();
        return true;
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 拒绝策略
     * @Date: 4:40 PM 2022/10/31
     **/
    private void reject(Runnable runnable) throws InternalException {
        switch (policy){
            case ABORT:throw new RuntimeException("task queue is full");
            case CALLER_RUN:runnable.run();break;
            case DISCARD_OLDEST:
                // 直接放弃这个任务
                return;
            case DISCARD:
                // 放弃等待时间最长的任务,也就是队列中的第一个任务
                taskQueue.poll();
                // 重新执行这个任务
                execute(runnable);
            default:break;
        }
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 实现Callable
     * @Date: 4:53 PM 2022/10/31
     **/
    public <V> RunnableFuture<V> submit(Callable<V> task) throws InterruptedException {
        checkPoolState();
        FutureTask<V> futureTask = new FutureTask<>(task);
        execute(futureTask);
        return futureTask;
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 强制关闭线程池
     * @Date: 4:41 PM 2022/10/31
     **/
    public synchronized void stop(){
        isStopped  = true;
        for (Worker worker:workers){
            worker.stopWorker();
        }
    }
​
    /**
     * @Author: Take-off
     * @Description: //TODO 正常关闭线程池
     * @Date: 4:46 PM 2022/10/31
     **/
    public synchronized void shutDown(){
        isStopped = true;
        waitForAllTasks();
        stop();
    }
​
    private void waitForAllTasks(){
        // 当线程池还有任务时,不退出循环
        while (taskQueue.size() > 0){
            Thread.yield();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    class Worker implements Runnable {
        private Thread thisThread;
        private final Runnable firstTask;
        private volatile boolean isStopped;
        public Worker(Runnable firstTask){
            this.firstTask = firstTask;
        }
​
        @Override
        public void run() {
            firstTask.run();
            thisThread = Thread.currentThread();
            while (!isStopped){
                try {
                    Runnable task = useTimed ? taskQueue.poll(keepAliveTime, unit) : taskQueue.take();
                    if (task == null){
                        int i;
                        boolean exit = true;
                        if (ct.get() > corePoolSize){
                            do {
                                i = ct.get();
                                if (i <= corePoolSize){
                                    exit = false;
                                    break;
                                }
                            }while (!ct.compareAndSet(i,i-1));
                            if (exit){
                                return;
                            }
                        }
                    }else {
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
​
        public synchronized void stopWorker(){
            if (isStopped){
                throw new RuntimeException("thread has been interrupted");
            }
            isStopped = true;
            thisThread.interrupt();
        }
    }
}

测试类

@Slf4j
public class TestPool {
​
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadPool pool = new ThreadPool(2, 5, TimeUnit.SECONDS, 10, RejectPolicy.ABORT, 100000);
        for (int i = 0; i< 10;i++){
            RunnableFuture<Integer> submit = pool.submit(() -> {
                log.info(Thread.currentThread().getName() + ": go");
                Thread.sleep(10);
                return 0;
            });
            log.info(submit.get() + "");
        }
        int n = 15;
        while (n--> 0){
            log.info("Number Threads = " + pool.getCt());
            Thread.sleep(1000);
        }
        pool.shutDown();
    }
}