不使用线程池时的一些问题
public class NonePool {
public static void main(String[] args) {
new Thread(){
public void run() {
//TODO
};
}.start();
}
}
上面的代码新建了一个线程,在线程结束的时候,线程会自动被回收
在生产环境中可能需要创建若干个线程来完成系统的功能,当需要的线程数量很多的时候就会需要分配大量的线程,此时还是使用这种方法就会有以下问题:
1. 创建大量的线程需要消耗大量的内存个CPU
2. 线程本身也需要占据大量的内存和CPU
3. 线程执行完后需要回收,回收的时候需要大量的GC操作
综上,在系统中,线程的数量并不是越多就越会提升系统的性能,需要有一个限度
线程池
为了避免频繁的创建线程和销毁线程,我们可以让创建的线程进行复用,类似于数据库连接池。
JDK中支持的线程池
- public static ExecutorService newFixedThreadPool(int nThreads)
含有固定数量为nThreads的线程的线程池,提交任务的时候,如果有线程则立即执行,没有的话则先将任务暂存在一个任务队列中 - public static ExecutorService newSingleThreadExecutor()
只有一个线程的线程池,若有多余的任务被提交,那么先将任务保存在一个队列中,待线程空闲,按照FIFO的策略执行下一个线程 - public static ExecutorService newCachedThreadPool()
返回一个可以根据实际情况调整线程数量的线程池。线程池的数量是不确定的,有空闲线程可以复用的话,那么就优先使用这些线程。如果没有空闲的线程,那么会创建新的线程处理任务,线程执行完毕后,将返回线程池进行服用,默认的情况下如果一个线程超过60秒没有被使用,那么就会被回收 - public static ScheduledExecutorService newSingleThreadScheduledExecutor()
线程池中线程的数量为1,不过返回的是ScheduledExecutorService对象,意味着可以在指定的时间执行任务
5.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
类似于第四种,不过可以指定线程中线程的数量
线程池案例分析
newFixedThreadPool
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
fixedThreadPool.submit(new MyTask(i));
}
}
}
class MyTask implements Runnable{
private int i;
public MyTask(int i) {
super();
this.i = i;
}
@Override
public void run() {
System.out.println(new Date().getTime()+"___"+Thread.currentThread().getId()+"___"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1497167121692___13___4
1497167121692___10___1
1497167121692___11___2
1497167121692___12___3
1497167121692___9___0
1497167122692___13___5
1497167122692___9___6
1497167122693___10___7
1497167122694___11___8
1497167122694___12___9
分析:
可以看出第一批执行的时间是1497167121692,第二批的五个是1497167122692,并且前5个的执行的线程的id与后5个是一样的,证明的确创建了5个线程,并且可以被复用
newScheduledThreadPool
scheduleAtFixedRate
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"\tbegin\t"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId()+"\tend\t"+System.currentTimeMillis());
}
}, 0, 2, TimeUnit.SECONDS);
}
}
public ScheduledFuture
9 begin 1497178292150
9 end 1497178293151
9 begin 1497178294151
9 end 1497178295152
11 begin 1497178296151
11 end 1497178297151
9 begin 1497178298151
9 end 1497178299151
注意
如果每个任务需要耗费的时间大于period,怎么办?
将上面的任务的休眠1s改为休眠8s,结果如下:
9 begin 1497178511226
9 end 1497178519227
9 begin 1497178519227
9 end 1497178527228
11 begin 1497178527229
11 end 1497178535229
9 begin 1497178535229
9 end 1497178543229
可以看出此时执行后续第一个任务(第一次执行不算)的时间是initialDelay+8s,执行后续第二个任务的时间是initialDelay+2*8s,也就是说在同一时刻只会有一个任务在实行,并且执行的周期为任务需要花费的时间与周期的大小之间去大值
scheduleWithFixedDelay
同样的上面的代码,如果将scheduledAtFixedRate改成scheduleWithFixedDelay,就会变成后续的任务会在前面的任务执行完毕后period时间执行,上面程序的运行结果就是
9 begin 1497178896324
9 end 1497178897324
9 begin 1497178899324
9 end 1497178900325
11 begin 1497178902326
11 end 1497178903326
9 begin 1497178905327
9 end 1497178906327
可以看出每一个任务的开始都是在上一个任务结束就的2秒钟才执行的