线程池基本学习

线程池基本学习

为什么使用线程池?

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

线程池思路

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。

线程池的好处

1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理

Executor、ExecutorService接口 以及 ThreadPoolExecutor类

线程池基本学习

Java定义了Executor接口并在该接口中定义了一个execute()方法用于执行一个线程任务。

public interface Executor {
    void execute(Runnable command);//执行任务、命令,没有返回值,一般用来执行Runnable
}

然后通过ExecutorService接口实现Executor接口并执行具体的线程操作,所以ExecutorService才是真正的线程池接口

public interface ExecutorService extends Executor {
   ...    
       
    <T> Future<T> submit(Callable<T> task);//执行任务,有返回值,一般又来执行Callable
    
    List<Runnable> shutdownNow();//关闭连接池
    
   ... 
        
}

ThreadPoolExecutor类 是我们真正使用的线程池类,它有四个构造方法,如下图:

线程池基本学习

可以直接看最后一个构造方法(原因:参数最多,最全面)

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
参数顺序 名称 类型 含义
1 corePoolSize int 核心线程池大小
2 maximumPoolSize int 最大线程池大小(前提是不能超过最大线程数量,否则该任务只能进入阻塞队列进行排队等候,直到有线程空闲了,才能继续执行任务)
3 keepAliveTime long 表示线程存活时间,除了核心线程外,那些被新创建出来的线程可以存活多久。意味着,这些新的线程一但完成任务,而后面都是空闲状态时,就会在一定时间后被摧毁。
4 unit TimeUnit 存活时间单位
5 workQueue BlockingQueue 表示任务的阻塞队列,由于任务可能会有很多,而线程就那么几个,所以那么还未被执行的任务就进入队列中排队,队列我们知道是 FIFO 的,等到线程空闲了,就以这种方式取出任务。这个一般不需要我们去实现。
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略(当任务太多来不及处理时,如何拒绝任务)

Executors 类是什么?

public class Executors extends Object

Executors是用来为Executor、ExecutorService 、ScheduledExecutorService、ThreadFactory、Callable类提供了一些工具方法。说白了Executors就是一个工具类、线程池的工厂类,用于创建并返回不同类型的线程池

常用的5种线程池

线程池基本学习

1.newFixedThreadPool (固定大小的线程池)

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

corePoolSize与maximumPoolSize相等都为nThreads,即其线程全为核心线程,就是一个固定大小的线程池并且任务执行是无序的

keepAliveTime = 0 该参数默认对核心线程无效,而FixedThreadPool全部为核心线程;

workQueue 为LinkedBlockingQueue(*阻塞队列),队列最大值为Integer.MAX_VALUE。如果任务提交速度持续大余任务处理速度,会造成队列大量阻塞。因为队列很大,很有可能在拒绝策略前,内存溢出。

// 案例代码
public class FixThreadPool {

    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        Fix fix = new Fix();
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);

        fixedThreadPool.shutdownNow();

    }
}

class Fix implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程启动");
    }
}

/* 运行结果如下:
pool-1-thread-2线程启动
pool-1-thread-3线程启动
pool-1-thread-1线程启动
可以看出,大小确实被固定了只有3个线程运行,而且执行顺序是无序的
*/

2.newSingleThreadExecutor (单个线程的线程池)

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

仔细看newSingleThreadExecutor()方法,其实就是 固定大小为 1 的线程池并通过FinalizableDelegatedExecutorService类包装了一次

//案例代码
public class SingleThreadExecutor {

    public static void main(String[] args) {


        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
        threadPoolExecutor.setMaximumPoolSize(2);
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
/* 运行结果如下:
    1
    2
   可以看出 FixedThreadPool类可以向下转型为ThreadPoolExecutor类,并重新对其线程池进行配置           
*/

        
/*
    fixedExecutorService2被FinalizableDelegatedExecutorService类包装后,无法成功向下转型。因此,fixedExecutorService2被定以后,无法修改,做到了真正的Single   
     以下语句会在 运行时异常 java.lang.ClassCastException:
 */
      ExecutorService fixedExecutorService2 = Executors.newSingleThreadExecutor();
      ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) fixedExecutorService2;

    }
}

3.newCachedThreadPool (可缓存的线程池)

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

corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制

keepAliveTime = 60s,线程空闲60s后自动结束。

workQueue 为 SynchronousQueue 同步队列,这个队列入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue;

4.newScheduledThreadPool(可做任务调度的线程池)

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);//1.点进去
}


public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());//2.点进去
}

//3.终点
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
     Executors.defaultThreadFactory(), defaultHandler);
}

可以看出 newScheduledThreadPool调用的是ScheduledThreadPoolExecutor的构造方法,而ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,构造是还是调用了其父类的构造方法。

newScheduledThreadPool 创建了一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务。

// 案例代码
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        System.out.println("延迟3秒执行");
    }
}, 3, TimeUnit.SECONDS);


scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("延迟3秒执行,并且1秒执行一次");
    }
}, 3, 1, TimeUnit.SECONDS);

5.newWorkStealingPool (足够大小的线程池)

public static ExecutorService newWorkStealingPool() {//点进入
    return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

//终点
public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode) {
    this(checkParallelism(parallelism),
         checkFactory(factory),
         handler,
         asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
         "ForkJoinPool-" + nextPoolId() + "-worker-");
    checkPermission();
}

newWorkStealingPool 是 JDK1.8 版本加入的一种线程池,它实现的一个线程池和上面4种都不一样,用的是 ForkJoinPool 类,它是一个并行的线程池,参数中传入的是一个线程并发的数量,这里和之前就有很明显的区别,前面4种线程池都有核心线程数、最大线程数等等,而这就使用了一个并发线程数解决问题,而且这个线程池也不会保证任务的顺序执行。

上一篇:20190619-James-快速鸟瞰并发编程, 呕心沥血整理的架构技术(第3篇)


下一篇:Photoshop合成深夜里手持火把在森林骑马的美女