一、new Thread的弊端
执行一个异步任务你还只是如下new Thread吗?
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
1、说说弊端:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
2、相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
3、线程池的作用
作用:主要用来解决线程生命周期开销问题和系统资源不足的问题。通过对多个任务重用线程,线程创建的开销就分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建线程带来的延迟。
优点:能立即为请求服务,提高了响应速度;可以通过调整线池中线程的数目防止出现资源不足的情况。
缺点:使用线程池构建的应用程序和其他多线程应用程序一样容易遭受并发错误,如同步错误和死锁;还容易遭受特定于线程池的其他少数风险,如与池有关的死锁,资源不足和线程泄漏、请求过载。
并发错误:线程池和其他并发机制依靠外套wait()和notify()方法,如果编码不正确,那么可能丢失通知,导致线程保持空闲状态。
死锁:一般的死锁,满足死锁的四个必要条件;池死锁,线程池中的所以线程都在执行已阻塞的等待队列中另一任务的执行结果的任务,而这个任务因为没有可以占用的线程而无法运行。
资源不足:如果线程池过大,被线程消耗的资源可能会严重影响系统性能;虽然线程之间切换的调度开销很小,但如果有很多线程,那么切换可能会严重地影响程序的性能。
线程泄漏:当从池中除去一个线程以执行任务,而任务结束后线程没有返回线程池。当任务抛出一个RuntimeException或一个Error时,如果池类没有捕获他们,那么线程只会退出线程池大小将永久减一,发生次数过多将导致线程池为空,系统将停止,没有线程可供使用;有些线程会永远等待某些资源或者用户输入,而这些资源不能保证变得可用,或者用户已经离开,如果一个线程永久的消耗着,那么相当于从池中除去,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。
请求过载:请求压垮服务器。可以简单地抛弃请求,依靠更高级别的协议稍后重试请求;也可以用服务器暂时很忙的响应来拒绝请求。
二、Executors提供四种线程池
newCachedThreadPool | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池的规模不存在限制。(数量不固定的线程池) |
---|---|
newFixedThreadPool | 创建一个固定长度线程池,可控制线程最大并发数,超出的线程会在队列中等待。(固定数量的线程池) |
newScheduledThreadPool | 创建一个固定长度线程池,支持定时及周期性任务执行。(定时线程池) |
newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。(单线程的线程池) |
将线程放入线程池的两种方法
1- ExecutorService 类中的 submit(Runnable task) submit(Callable<T> task)
2- Executor 接口中的 execute(Runnable command)
submit() 和 execute() 的区别
submit()方法,可以提供Future < T > 类型的返回值。
executor()方法,无返回值。
这篇博文中有详细介绍两者的区别: https://blog.csdn.net/hayre/article/details/53314599
如果想深入理解线程池,我在网上找到了一篇不错的博文
理解Java并发工具包线程池的设计:https://blog.csdn.net/u010454030/article/details/82668715
问:为什么要使用线程池,以及线程池的运行流程
答:基于多核CPU的发展,使得多线程开发日趋流行。然而线程的创建和销毁,都涉及到系统调用,比较消耗系统资源,所以就引入了线程池技术,避免频繁的线程创建和销毁。
在Java用有一个Executors工具类,可以为我们创建一个线程池,其本质就是new了一个ThreadPoolExecutor对象。
建议使用较为方便的 Executors 工厂方法来创建线程池。
- Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
- Executors.newFixedThreadPool(int)(固定大小线程池)
- Executors.newSingleThreadExecutor()(单个后台线程)
- Executors.newScheduledThreadPool() (支持计划任务的线程池)
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。
三、使用方法
1、newCachedThreadPool
创建数量不固定的线程池
static ExecutorService | newCachedThreadPool() 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。 |
static ExecutorService | newCachedThreadPool(ThreadFactory threadFactory) 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。 |
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 具有缓冲功能的线程池,系统根据需要创建线程,线程会被缓冲到线程池中
* 如果线程池大小超过了处理任务所需要的线程线程池就会回收空闲的线程池,
* 当处理任务增加时,线程池可以增加线程来处理任务线程池不会对线程的大
* 小进行限制线程池的大小依赖于操作系统
*/
public class Cached {
public static void main(String[] args){
ExecutorService ex = Executors.newCachedThreadPool();
for(int i=0; i<5; i++){
runnable rn = new runnable();
ex.execute(rn);
}
ex.shutdown();
}
}
class runnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
package com.th.threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2018/6/6.
* newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,
* 若无可回收,则新建线程。线程池的规模不存在限制。
*/
public class NewCachedThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1- 在未来某个时间执行给定的命令。
// 该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
//2- 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
// 该 Future 的 get 方法在成功完成时将会返回给定的结果
cachedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
cachedThreadPool.shutdown();
}
}
2、newFixedThreadPool
创建固定数量的线程池
static ExecutorService | newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。 |
static ExecutorService | newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。 |
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 创建具一个可重用的,有固定数量的线程池
* 每次提交一个任务就提交一个线程,直到线程达到线城池大小,就不会创建新线程了
* 线程池的大小达到最大后达到稳定不变,如果一个线程异常终止,则会创建新的线程
*/
public class newThreadpool {
private static int pool = 2;
public static void main(String[] args){
ExecutorService ex = Executors.newFixedThreadPool(pool);
for(int i=0; i<5; i++){
runnable rn = new runnable();
ex.execute(rn);
}
ex.shutdown();
}
}
class runnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
package com.th.threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2018/6/6.
*
* newFixedThreadPool 创建一个固定长度线程池,可控制线程最大并发数,超出的线程会在队列中等待。
*/
public class NewFixedThreadPool {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++)
{
final int index = i;
//1- 在未来某个时间执行给定的命令。
// 该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
threadRunMethod(index);
}
});
//2- 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
// 该 Future 的 get 方法在成功完成时将会返回给定的结果
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
threadRunMethod(index);
}
});
}
fixedThreadPool.shutdown();
}
/**
*
* @param index
*/
private static void threadRunMethod(int index) {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、n
3、newScheduledThreadPool
创建定时线程池
static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*
* 创建一个线程池,大小可以设置,此线程支持定时以及周期性的执行任务定时任务
*/
public class Scheduled {
private static int pool = 2;
public static void main(String[] args){
ScheduledExecutorService ex = Executors.newScheduledThreadPool(pool);
runnable rn = new runnable();
//参数1:目标对象 参数2:隔多长时间开始执行线程, 参数3:执行周期 参数4:时间单位
ex.scheduleAtFixedRate(rn, 3, 1, TimeUnit.MILLISECONDS);
}
}
class runnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
package com.th.threadPool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Created by Administrator on 2018/6/6.
* newScheduledThreadPool 创建一个固定长度线程池,支持定时及周期性任务执行。
*/
public class NewScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//testSchedule(scheduledExecutorService);
//testScheduleAtFixedRate(scheduledExecutorService);
testScheduleWithFixedDelay(scheduledExecutorService);
// 终止线程池
//scheduledExecutorService.shutdown();
}
/**
*
* 跟 testScheduleAtFixedRate 非常类似,就是延迟的时间有点区别
* 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;
* 也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,
* 接着在 initialDelay + 2 * period 后执行,依此类推。
*
* 如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
* 推迟的时间 : 等到上次的任务执行完之后再延迟period 的时间后执行。
* @param scheduledExecutorService
*/
private static void testScheduleWithFixedDelay(ScheduledExecutorService scheduledExecutorService) {
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
System.out.println("延迟2秒,再3秒执行一次");
//如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
//本次任务执行完后下次的任务还需要延迟period时间后再执行
Thread.sleep(6*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},2,3,TimeUnit.SECONDS);
}
/**
* 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;
* 也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,
* 接着在 initialDelay + 2 * period 后执行,依此类推。
*
* 如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
* 推迟的时间 : 等到上次的任务执行完就立马执行。
* @param scheduledExecutorService
*/
private static void testScheduleAtFixedRate(ScheduledExecutorService scheduledExecutorService) {
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
System.out.println("延迟2秒,再3秒执行一次");
//如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
//如果任务里面执行的时间大于 period 的时间,本次任务执行完后,下次任务立马执行。
Thread.sleep(6*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},2,3,TimeUnit.SECONDS);
}
/**
* 创建并执行在给定延迟后启用的一次性操作
* @param scheduledExecutorService
*/
private static void testSchedule(ScheduledExecutorService scheduledExecutorService) {
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
}
4、newSingleThreadExecutor
创建单线程的线程池
static ExecutorService | newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 |
static ExecutorService | newSingleThreadExecutor(ThreadFactory threadFactory) 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,并在需要时使用提供的 ThreadFactory 创建新线程。 |
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 创建只有一个线程的线程池
*/
public class Single {
public static void main(String[] args){
ExecutorService ex = Executors.newSingleThreadExecutor();
for(int i=0; i<5; i++){
runnable rn = new runnable();
ex.execute(rn);
}
ex.shutdown();
}
}
class runnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
package com.th.threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Administrator on 2018/6/6.
* newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
* 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
*/
public class NewSingleThreadExecutor {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
/*singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("newSingleThreadExecutor: " + index);
Thread.sleep(2*1000);
} catch (Exception e) {
e.printStackTrace();
}
}
});*/
singleThreadExecutor.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("newSingleThreadExecutor: " + index);
Thread.sleep(2*1000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
}
}