共计 5548 个字符,预计需要花费 14 分钟才能阅读完成。
一、为什么须要线程池
二、线程池的分类
1、newCachedThreadPool
1、创立一个可依据须要创立新线程的线程池,然而在以前结构的线程可用时将重用它们,并在须要时应用提供的 ThreadFactory 创立新线程
2、特色:(1)线程池中数量没有固定,可达到最大值 (Interger.MAX_VALUE)
(2) 线程池中的线程可进行缓存反复理由和回收 (回收默认工夫为一分钟)
(3) 当线程池中没有可用线程,会从新创立一个线程
示例
public class CacheThreadPoolDemo {public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i<20; i++){executorService.execute(new Task());
}
}
}
2、newFixedThreadPool
1、创立一个可重用固定线程数的线程池,以共享的无界队列形式来运行这些线程,在任意点,在大多数 nThreads 线程会处于解决工作的活动状态,如果在所有线程处于活动状态时提交附加工作,则在有可用线程之前,附加工作将在队列中期待,如果在敞开前的执行期间因为失败而导致任何线程终止,那么一个新线程将代替他执行后续的工作(如果须要),在某个线程被显式的敞开之前,池中的线程将始终存在
2、特色(1)线程池中的线程处于肯定的量,能够很好的控制线程的并发量
(2) 线程能够反复被应用,在显式敞开之前,都将始终存在
(3) 超出一定量的线程被提交时须要在队列中期待
示例
public class FixedThreadPoolDemo {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(5);
for(int i = 0; i<20; i++){service.execute(new Task());
}
service.shutdown();}
}
3、newSingleThreadExecutor
1、创立一个应用单个 worker 线程的 Executor,以无界队列形式来运行该线程,(留神,如果因为在敞开前的执行期间呈现失败而终止了此单个线程,那么如果须要,一个新线程将代替它执行后续的工作),可保障程序地执行各个工作,并且在任意给定的工夫不会有多个线程是流动的。与其余等效的 newFixedThreadPool(1)不同,可保障无需重新配置此办法所返回的执行程序即可应用其余线程
2、特色(1)线程池中最多执行 1 个线程,之后提交的线程流动将会在队列中顺次执行
示例
public class SingleThreadPoolDemo {public static void main(String[] args) {ExecutorService serice = Executors.newSingleThreadExecutor();
for(int i= 0; i<20; i++){serice.execute(new Task());
}
serice.shutdown();}
}
4、newSingleThreadScheduledExecutor
1、创立一个单线程执行程序,它可安顿在给定提早后运行命令或者定期地执行
2、特色(1)线程池中最多执行 1 个线程,之后提交的线程流动将会排在队列中顺次执行
(2) 可定时或者提早执行线程流动
示例
用法与 newScheduledThreadPool 一样,只不过线程池外面只有一个线程
public class SingleThreadScheduledExecutorDemo {public static void main(String[] args) {ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.schedule(new Runnable() {
@Override
public void run() {System.out.println("提早 3 秒执行");
}
},3, TimeUnit.SECONDS);
service.shutdown();}
}
5、newScheduledThreadPool
1、创立一个线程池,它可安顿在给定提早后运行命令或者定期执行
2、特色(1)线程池中具备指定数量的线程,即使是空线程也将保留
(2) 可定时或者提早执行线程流动
示例
public class ScheduledThreadPoolDemo {public static void main(String[] args) {ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
service.schedule(new Runnable() {
@Override
public void run() {System.out.println("提早 3 秒执行");
}
},3, TimeUnit.SECONDS);
service.shutdown();}
}
public class ScheduledThreadPoolDemo2 {public static void main(String[] args) {ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {System.out.println("1---- 提早 1 秒执行, 每 3 秒执行一次");
}
},1,3, TimeUnit.SECONDS);
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {System.out.println("2---- 提早 1 秒执行, 每 3 秒执行一次");
}
},1,3, TimeUnit.SECONDS);
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {System.out.println("3---- 提早 1 秒执行, 每 3 秒执行一次");
}
},1,3, TimeUnit.SECONDS);
// service.shutdown();}
}
6、newWorkStealingPool
1、创立一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认以后零碎的 CPU 个数
示例
public static void main(String[] args) {
// 设置并行级别为 2, 即默认每时每刻只有 2 个线程同时执行
ExecutorService service = Executors.newWorkStealingPool(8);
for(int i = 1; i<=10; i++){
final int count = i;
service.submit(new Runnable() {
@Override
public void run() {Date now = new Date();
System.out.println("线程"+Thread.currentThread()+"实现工作:"
+count+"工夫为:"+now.getSeconds());
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
});
}
while (true){// 主线程陷入死循环, 来察看后果, 否则看不到后果的}
}
}
三、线程池的生命周期
四、线程池的创立
五、阻塞队列
1、ArrayBlockingQueue
1、基于数组的阻塞队列实现,在 ArrayBlockingQueue 外部,保护了一个定长数组,以便缓存队列中的数据对象,这是一个罕用的阻塞队列,除了一个定长的数组外,ArrayBlockingQueue 外部还保留着两个整型变量,别离示意着对垒的头部和尾部在数组中的地位
2、ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无奈真正并行运行,这点尤其不同于 LinkedBlockingQueue;依照实现原理来剖析,ArrayBlockingQueue 齐全能够采纳拆散锁,从而实现生产者和消费者操作的齐全并行运行,Doug Lea 之所以没有这样去做,兴许是因为 ArrayBlockingQueue 的数据写入和获取操作曾经足够笨重,以至于引入独立的锁机制,除了给代码带来额定的复杂性外,其在性能上齐全占不到任何便宜
3、ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个显著的不同之处在于,前者在插入和删除元素时不会产生或销毁任何额定的对象实例,而后者则会生产一个额定的 Node 对象,这在长时间内须要高效并发地解决大批量数据的零碎中,其对于 GC 的影响还说存在肯定的区别,而在创立 ArrayBlockingQueue 时,咱们还能够管制对象外部锁是否采纳偏心锁,默认采纳非偏心锁
2、LinkedBlockingQueue
1、基于链表的阻塞队列,同 ArrayBlockingQueue 相似,其外部也维持着一个数据缓冲队列(该队列由一个链表形成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列外部,而生产者立刻返回,只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue 能够通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中生产掉一份数据,生产者线程被唤醒,反之对于消费者这端的解决也基于同样的原理,而 LinkedBlockingQueue 之所以可能高效的解决并发数据,还因为其对于生产者端和消费者端别离采纳了独立的锁来控制数据同步,这也意味着在高并发的状况下生产者和消费者能够并行的操作队列中的数据,以此来进步整个队列的并发性能
3、DelayQueue
1、DelayQueue 中的元素只有当其指定的延迟时间到了,才可能从队列中获取到该元素,DelayQueue 是一个没有大小限度的队列,因而往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞
2、应用场景:DelayQueue 应用场景较少,但都相当奇妙,常见的例子比方应用一个 DelayQueue 来治理一个超时未响应的连贯队列
4、PriotityBlockingQueue
1、基于优先级的阻塞队列(优先级的判断通过构造函数传入的 Compator 对象来决定),但须要留神的是 PriotityBlockingQueue 并不会阻塞数据生产者,而只会在没有可生产的数据时,阻塞数据的消费者,因而应用的时候要特地留神,生产者生产数据的速度相对不能快于消费者生产数据的速度,否则工夫一长,会最终耗尽所有可用的堆内存空间
2、在实现 PriotityBlockingQueue 时,外部控制线程同步的锁采纳的是偏心锁
5、SynchronousQueue
1、一种无缓冲的期待队列,相似于无中介的间接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的间接生产者,如果一方没有找到适合的指标,那么对不起,大家都在集市期待,绝对于有缓冲区的 BlockingQueue 来说,少了一个两头经销商的环节(缓冲区),如果有经销商,生产者间接把产品零售给经销商,而无需在意经销商最终会将这些产品卖给哪些消费者,因为经销商可用库存一部分商品,因而绝对于间接交易模式,总体来说采纳两头经销商的模式会吞吐量高一些(可用批量交易);但另一方面,又因为经销商的引入,使得产品从生产者到消费者两头减少了额定的交易环节,单个产品的及时响应性能可能会升高。
2、申明一个 SynchronousQueue 有两种不同的形式,他们之间有着不太一样的行为,偏心模式和非偏心模式的区别:(1)如果采纳偏心模式:SynchronousQueue 会采纳偏心锁,并配合一个 FIFO 队列来阻塞多余的生产者和消费者,从而体系整体的偏心策略
(2) 如果是非偏心模式:(SynchronousQueue 默认):SynchronousQueue 采纳非偏心锁,同时配合 LIFO 队列来治理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易呈现饥渴的状况,即可能有某些生产者和消费者的数据永远得不到解决
6、ArrayBlockingQueue 和 LinkedBlockingQueue 的区别
1、队列中的锁的实现不同
(1)ArrayBlockingQueue 实现的队列中的锁是没有拆散的,即生产者和消费者用的是同一个锁;
(2)LinkedBlockingQueue 实现的队列中的锁是拆散的,即生产用的是 putLock, 生产用的是 takeLock
2、队列大小初始化形式不同
(1)ArrayBlockingQueue 实现的队列中必须指定队列的大小
(2)LinkedBlockingQueue 实现的队列中能够不指定队列的大小,然而默认是 Ingeger.MAX_VALUE
六、回绝策略