关于线程池

1. 为什么不能用Executors创建线程池?

1.1 CachedThreadPool可缓存的线程池

两种实现方式 :

Executors.newCachedThreadPool();

Executors.newCachedThreadPool(new TaskThreadFactory("" , false , 2));

默认实现 : new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                             60L, TimeUnit.SECONDS,
                             new SynchronousQueue<Runnable>(),
                             threadFactory);

因为最大连接数是Integer.MAX_VALUE . 当访问请求过大时 , 易造成OOM

1.2 FixedThreadPool(固定大小的线程池)

Executors.newFixedThreadPool(1);

Executors.newFixedThreadPool(1 , new TaskThreadFactory("" , false , 2));

默认实现方式 :

new ThreadPoolExecutor(nThreads, nThreads,
                             0L, TimeUnit.MILLISECONDS,
                             new LinkedBlockingQueue<Runnable>());

因为LinkedBlockingQueue队列的最大值为Integer.MAX_VALUE , 可以认为是无界队列 , 也就可以往队列中插入无限多的任务 , 也会造成OOM

1.3 ScheduledThreadPool(可定时执行线程池)

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


public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

因为只支持了两种实现方式 , 且参数只有 : 核心线程数和工厂两种 , 其最大连接数默认为Integer.MAX_VALUE , 一样会造成内存过大

1.4 SingleThreadExecutor(单线程线程池)

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

同FixedThreadPool一样 , 因为其用的LinkedBlockingQueue为无界队列 , 其队列最大容量默认为Integer.MAX_VALUE , 资源不允许的情况下 , 易造成OOM .

1.5 无界队列和有界队列

常见的有界队列 :

  • ArrayBlockingQueue: 基于数组实现的阻塞队列 
  • LinkedBlockingQueue 当设置其容量时也是有界队列 , 但是在Executors中默认为Integer.MAX_VALUE , 此时可以理解为是无界队列 . 基于链表实现的
  • ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者
  • LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

共同点 :

  • put take 操作都是阻塞的
  • offer poll 操作不是阻塞的,offer 队列满了会返回false不会阻塞,poll 队列为空时会返回null不会阻塞
  • 补充一点,并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用CPU,在有些场景也是需要阻塞的,put take 存在必有其存在的必然性

无界队列

  • PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。 
  • DealyQueue:一个使用优先级队列实现的无界阻塞队列。 
  • SynchronousQueue:一个不存储元素的阻塞队列,也可理解为无界阻塞队列 .
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。 

共同点 :

  • put 操作永远都不会阻塞,空间限制来源于系统资源的限制
  • 底层都使用CAS无锁编程

1.6 公平锁和非公平锁

公平锁 : 多个线程按照申请锁的顺序去获得锁 , 线程会进入队列 , 遵循FIFO的原则去获取锁 .

优点 : 可保证线程有序进行 , 且所有线程都会获得目标资源 , 不会饿死在 .

缺点 : 对性能损耗较大 , 吞吐量会下降 . 除了第一个线程,其他的线程都会阻塞,

非公平锁 : 多个线程共同去获取锁的时候 , 会先去争取锁 , 获取不到的情况下 , 才进入队列等待 .

优点 : 减少CPU唤醒线程的开销 , 吞吐量会高一些 , CPU不必去唤醒所有的线程 , 减少唤醒线程的数量 .

缺点 : 无序 . 且可能导致某些线程一直获取不到锁 . 导致线程失效的情况 .

常见类 : FairSync和NofairSync

1.7 线程的生命周期

  1. 新建(NEW):新创建了一个线程对象。
  2. 可运行(RUNNABLE): 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种: 

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  1. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可
posted @ 2022-03-24 16:40  每天学习1点点  阅读(28)  评论(0编辑  收藏  举报