线程池详解

132 阅读4分钟

一、环境

工欲善其事必先利其器,先创建环境,new Project --> 选好目录和Jdk版本号 --> 进去后新建module --> 也选好版本号。

二、背景介绍

如果不使用线程池,每个任务都会新开一个线程负责处理。比如现在这样:

public class EvertTaskOneThread {

    public static void main(String[] args) {
        Thread thread = new Thread(new Task());
        thread.start();
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println("执行了该程序");
        }
    }
}

多个任务:

public class EveryTaskLoopThread {

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            Thread thread = new Thread(new Task());
            thread.start();
        }
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "执行了该程序");
        }
    }
}

但是使用线程池有以下好处:

  1. 避免反复创建线程,系统开销过大,创建、维护调用、关闭都会对系统有巨大影响
  2. 因为避免了创建和关闭的开销,响应速度变快了

三、创建和停止线程池

1. 线程池构造函数的参数

image.png

coolPoolSize和maxPoolSize

最好的例子就是银行排队:

一般银行不是所有窗口都开(maxPoolSize),而是只开启一部分,这就是corePoolSize,当超过当前corePoolSize,会把任务放到队列中去,放不下了才ru开启新线程去运行任务。如果队列满了,且达到了maxPoolSize就执行拒绝策略。

其他特点:

  1. 当coolPoolSize和maxPoolSize相同,就等于创建了固定大小的线程池(不会变)
  2. 线程池希望保持的数量少,只有负载很大的时候才增加它
  3. 当maxPoolSize设置为Integer.MAX_VALUE,可以容纳任意数量的并发任务
  4. 设置队列为无界队列(如LinkedBlockingQueue),线程就不会超过coolPoolSize

keepAliveTime

超过coolPoolSize达到一定程度会继续创建线程,但是只管创建不行,还得在不忙的时候回收掉,超过的时间就是keepAliveTime。

ThreadFactory

用来创建线程的,当要添加线程时就可以用这个。默认是使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组中,有相同的NORM_PRIORITY优先级,而且都不是守护线程。当然这个也可以自己设置。

查看源码:

Executors.defaultThreadFactory();

//最后是走这个方法
DefaultThreadFactory() {
    SecurityManager s = System.getSecurityManager();
    group = (s != null) ? s.getThreadGroup() :
    Thread.currentThread().getThreadGroup();
    namePrefix = "pool-" +
        poolNumber.getAndIncrement() +
        "-thread-";
}

//创建现成的时候会调用newThread方法
public Thread newThread(Runnable r) {
    Thread t = new Thread(group, r,
                          namePrefix + threadNumber.getAndIncrement(),
                          0);
    if (t.isDaemon())
        t.setDaemon(false);
    if (t.getPriority() != Thread.NORM_PRIORITY)
        t.setPriority(Thread.NORM_PRIORITY);
    return t;
}

workQueue

三种队列类型:

  1. 直接交接:SynchronousQueue
  2. 无界队列:LinkedBlockingQueue
  3. 有界队列:ArrayBlockingQueue

2. 手动创建还是自动创建线程池

手动创建可以字节设置规则,自动创建比较快捷。

JUC常见的线程池:

newFixThreadPool

public class FixedThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 1000; i++) {
            service.execute(new Task());
        }
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

不难发现,所有的程序都是在上面5个线程中执行的。

也可以看一下源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

这是其中的一些参数设置。

但是使用nexFixedThreadPool是有一些问题的,传进去的LinkedBlockingQueue容量没上限,当请求过多没法及时处理的时候,内存占用过多就会报OOM。

public class FixedThreadPoolOOM {
    private static ExecutorService executorService = Executors.newFixedThreadPool(1);

    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(new SubThread());
        }
    }
}

class SubThread implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

image.png

最后会报错:

image.png

说明这种方式是有瑕疵的。

newSingleThreadPool

示例:

public class SingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            service.execute(new FixedThreadPoolTest.Task());
        }
    }
}

源码如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

这个线程池和上面newFixThreadPool也很类似,只是线程数是1。

newCachedThreadPool

特点是可以缓存线程池,具有自动回收多余线程的功能。

先看源码:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

前面介绍了SynchronousQueue是一个直接交换的队列,也就是队列容量是0,也就是来了任务会直接交给线程执行,没用到的线程会被回收掉。

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            service.execute(new FixedThreadPoolTest.Task());
        }
    }
}

结果如下:

image.png

这里的问题是,可能会创建非常多的线程,造成OOM。

newScheduledThreadPool

先看源码:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

支持定时及周期性任务执行的线程池。

public class ScheduledTreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
        service.schedule(new FixedThreadPoolTest.Task(),5, TimeUnit.SECONDS);
    }
}

这里是可以设置隔一段时间执行的,执行上面的指令可以看到。

语法:service.schedule(task,time,timeunit);

线程池的线程数量设置为多少合适?

主要还是corePoolSize和maxPoolSize:

  • CPU密集型:最佳线程数为CPU核心数的1-2倍左右
  • 耗时IO型:最佳线程数一般大于CPU核心数很多倍

线程数=CPU核心数×(1+平均等待时间/平均工作时间)。

线程池的对比

image.png

上面的意思是60sec后回收。ScheduledThreadPool的keepAliveTime是0sec,上面写错了。

workStealingPool

Java1.8的时候加入的。用的比较少,任务可以产生子任务可以使用这个,比如树的查询,矩阵的划分等,这种线程池有窃取的能力,比如A线程产生多个子任务,子任务是分配到线程自己的队列中去执行的,如果A线程中任务有点多,别的线程空闲时可以帮忙处理。

3、停止线程

几个方法:

shutdown

停止线程,但并不一定真的停止,只是初始化停止线程的步骤,接下来再有任务过来直接拒绝

package threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author :16140
 * @description :
 * @create :2022-08-02 22:24:00
 */
public class ShutDown {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            service.execute(new ShutDownTask());
        }
        //让它运行1.5s
        Thread.sleep(1500);
        //shutdown该线程看看是否真的关闭了
        service.shutdown();
        //测试shutdown了以后,再交线程会不会报错
        service.execute(new ShutDownTask());
    }
}

class ShutDownTask implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果确实,shutdown以后再执行线程报错:

image.png

isShutDown

判断线程是否shutdown了

package threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author :16140
 * @description :
 * @create :2022-08-02 22:24:00
 */
public class ShutDown {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            service.execute(new ShutDownTask());
        }
        //让它运行1.5s
        Thread.sleep(1500);
        System.out.println(service.isShutdown());
        //shutdown该线程看看是否真的关闭了
        service.shutdown();
        System.out.println(service.isShutdown());
        //测试shutdown了以后,再交线程会不会报错
        service.execute(new ShutDownTask());
    }
}

class ShutDownTask implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

isTerminated

查看线程是否完全终止了

service.isTerminated();

再试一下,当所有任务执行完毕是否为true:

package threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author :16140
 * @description :
 * @create :2022-08-02 22:24:00
 */
public class ShutDown {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            service.execute(new ShutDownTask());
        }
        //让它运行1.5s
        Thread.sleep(1500);
        System.out.println(service.isShutdown());
        //shutdown该线程看看是否真的关闭了
        service.shutdown();
        System.out.println(service.isShutdown());
        System.out.println(service.isTerminated());
        Thread.sleep(10000);
        System.out.println(service.isTerminated());
        //测试shutdown了以后,再交线程会不会报错
//        service.execute(new ShutDownTask());
    }
}

class ShutDownTask implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

isShutdown只是判断是否开始停止了,isTerminated是判断是否完全终止了。

awaitTermination

等待一段时间,看线程是否彻底终止了,这个方法会把这个线程锁住,(不是执行的线程)

System.out.println(service.awaitTermination(10L, TimeUnit.SECONDS));

shutdownNow

把线程立刻关闭

package threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author :16140
 * @description :
 * @create :2022-08-02 22:24:00
 */
public class ShutDown {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            service.execute(new ShutDownTask());
        }
        //让它运行1.5s
        Thread.sleep(700);
        service.shutdownNow();

class ShutDownTask implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "被中断了");
        }
    }
}

结果如图:

image.png

返回runnableList:

image.png