Java开发中启动一个线程最简单的方法就是new Thread,如下:
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
这种写法缺陷多多,比如:
1)每次new Thread新建对象,性能开销很大;
2)线程缺乏统一管理,可能无限制新建线程,相互之间竞争,并且可能占用过多系统资源进而导致死机或者OOM;
3)缺乏更多功能,如定时执行、定期执行、线程中断。
针对以上诸多缺陷,Java提供了四种线程池:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。四种线程池详细解释如下:
1)newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
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();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
线程池为无限大,当执行第二个任务时,如果第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
2)newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程在队列中等待
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
因为线程池大小为3,每个线程输出index后sleep 2s,所以没2s打印3个数字。
定长线程池的大小最好根据系统资源设置,如Runtime.getRuntime.availableProcessors()。
3)newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
标识延迟3s执行。
定期执行代码示例如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
表示延迟1s后,每3s执行一次。
ScheduledExecutorService比Timer更安全,功能更强大。
4)newSingleThreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行
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(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
结果一次输出,相当于顺序执行各个任务。
现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞及影响UI线程响应的操作。
线程池的作用:
线程池的作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费系统资源,多了造成系统拥挤效率低下。用线程池控制线程数量,其它线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行。若队列中没有等待的线程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
使用线程池的好处:
1)减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可以执行多个任务;
2)可以根据系统的承受能力,调整线程池中工作线程的书目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
ExecutorService | 真正的线程池接口。 |
ScheduledExecutorService | 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。 |
ThreadPoolExecutor | ExecutorService的默认实现。 |
ScheduledThreadPoolExecutor | 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。 |
无论创建哪种线程池,都要调用ThreadPoolExecutor。
线程池类为java.util.concurrent.ThreadPoolExecutor,常用构造方法为:
ThreadPoolExecutor(int corePoolSize, int maxmumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,
RejectedExecutionHandler handler)
corePoolSize:线程池维护的最少线程数量
maxmumPoolSize:线程池维护的最大线程数量
keepAliveTime:线程池维护线程所允许的最大空闲时间
unit:线程池维护线程锁允许的线程空闲时间单位
workQueue:线程池所使用的缓冲队列
handler:线程池对拒绝任务的处理策略
一个任务通过execute(Runnable)方法添加到线程池,任务就是一个Runnable类型的对象,任务的执行方法就是Runnable类型对象的run90方法。
当一个任务通过execute(Runnable)欲添加到线程池时,假设线程池中线程数量为N:
如果 N < corePoolSize,即使线程池中的线程都处于空闲状态,也要添加新的线程来处理被添加的任务;
如果 N = corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列;
如果 N > corePoolSize,且workQueue已满,N < maxmumPoolSize,建新的线程来处理新添加的任务;
如果 N > corePoolSize,且workQueue已满,N = maxmumPoolSize,通过handler指定的策略来处理此任务。
也就是,处理任务的优先级为:
核心线程corePoolSize,任务队列workQueue,最大线程maxmumPoolSize,如果三者都满了,则使用handler处理被拒绝的任务。
当线程池中的数量大于corePoolSize时,如果某个线程的空闲时间大于keepAlive,线程将被终止。这样,线程池可以动态调整池中的线程数。
unit可选的参数为java.util.concurrent.Timeunit中的几个静态属性:
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
workQueue常用的是:java.util.concurrent.ArrayBlockingQueue
handler策略有以下4个:
1)ThreadPoolExecutor.AbortPolicy()
抛出java.util.concurrent.RejectedExecutionException()异常
2)ThreadPoolExecutor.CallerRunsPolicy
重试添加当前任务,他会自动重复调用execute()方法
3)ThreadPoolExecutorr.DiscardOldestPolicy
抛弃旧的任务
4)ThreadPoolExecutor.DiscardPolicy
抛弃当前任务
参考文档:https://www.cnblogs.com/baizhanshi/p/5469948.html