在开发过程中经常会遇到并发量比较高的代码,可以用多线程去解决的。除了我们常用的自定义线程池ThreadPoolExecutor还有几种Java封好的:FixedThreadPool(固定个数线程池)、CachedThreadPool(弹性线程池)、SingleThreadExecutor(单线程线程池)、ScheduleThreadPool(定时器线程池)、WorkStealingPool(任务窃取线程池)、ForkJoinPool(分支合并线程池)
FixedThreadPool(固定个数线程池)
有一个参数:核心线程数和最大线程数。在关闭之前等待所有的线程执行完毕。且不会自动销毁,需要手动关闭。看以下代码
public static void main(String[] args) {
ExecutorService service=Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
int finalI = i;
service.submit(()->{
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(finalI+"---"+Thread.currentThread().getName());
});
}
service.shutdown();
}
运行结果:
尝试运行可以发现5秒钟前五个线程打印,再等5秒后五个线程一起打印。
就以上代码来说,当前活跃的线程是5个,且在关闭之前会等待所有的线程执行完毕。
适用场景:稳定的固定的正规的并发线程,多用于服务器。
CachedThreadPool(弹性线程池)
默认空线程池,来一个任务就起一个线程,系统能支撑多少个就可以起多少个。线程存活时间是60s,60s无任务之后线程销毁。
public static void main(String[] args) throws InterruptedException {
ExecutorService service= Executors.newCachedThreadPool();
for(int i=0;i<2;i++){
service.execute(()->{
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println(service);//容量2,运行线程数2
TimeUnit.SECONDS.sleep(70);//60s无任务线程销毁,所以设置等待70s
System.out.println(service);//容量0,运行线程数0
}
运行结果:
适用场景:大量段生命周期的异步任务
SingleThreadExecutor(单线程线程池)
线程池中只有一个线程。可以保证任务按照先后顺序执行。
public static void main(String[] args) {
ExecutorService service= Executors.newSingleThreadExecutor();
for(int i=0;i<5;i++){
final int j=i;
service.execute(()->{
System.out.println(j+"---"+Thread.currentThread().getName());
});
}
}
运行结果:
它可以保证任务是按照“先来先服务”的原则进行的。
适用场景:多个任务访问同一个资源,例如多个线程访问同一个文件系统时。
这里有的人可能会有疑问,既然是先来先服务为什么还要用线程池,直接执行不就ok了?
想一下线程池最大的优点是什么,线程的“重复利用”。起线程关线程是很费资源的。这个单线程线程池虽然也是一个时间只有一个任务执行,但是线程也只有一个呀。避免了重复的起关线程所消耗的资源。
ScheduleThreadPool(定时器线程池)
一个参数:核心线程数
在任务启动的时候可以指定when,how long去执行。一定意义上可以代替timer
任务启动时候有四个参数,如下代码:
public static void main(String[] args) {
ScheduledExecutorService service= Executors.newScheduledThreadPool(4);//核心线程4个
service.scheduleAtFixedRate(()->{
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
},0,50,TimeUnit.MILLISECONDS);
//第一个参数:Runnable任务
//第二个参数:第一个任务执行的时候延迟多长时间。
//第三个参数:每隔多长时间执行一次这个任务
//第四个参数:时间单位
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
}
这段代码意思:从执行到这里开始(第一个参数0)任务启动,每隔50毫秒(第二个和第三个参数)执行一次,等3秒后任务关闭。
适用场景就是定时任务啦。
WorkStealingPool(任务窃取线程池)
当线程空闲的时候会主动的去拿任务,且只要虚拟机不结束就一直运行,因为产生的线程都是精灵线程。
这有一个精灵线程的概念:
精灵线程:又叫守护线程、后台线程,指的是本身无法使虚拟主机保持活动的线程。
public static void main(String[] args) throws IOException {
ExecutorService service= Executors.newWorkStealingPool();
System.out.println(Runtime.getRuntime().availableProcessors());
service.execute(new R(1000));
service.execute(new R(2000));
service.execute(new R(2000));
service.execute(new R(2000));
service.execute(new R(2000));
//由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话看不到输出
System.in.read();
}
static class R implements Runnable{
int time;
R(int t){
this.time=t;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(time+" "+Thread.currentThread().getName());
}
}
运行结果:
如果不阻塞,即不加System.in.read()的话,及时shutdown也看不到输出的。
适用场景:比较耗时的任务。
ForkJoinPool(分支合并线程池)
可以想一下归并排序。
这个线程池会把任务拆分成过个小任务去执行,然后再把结果合并。
比如1000000个100以内的随机数相加,如下代码:
static int[] nums=new int[1000000];
static final int MAX_NUM=50000;
static Random r=new Random();
static{
for(int i=0;i<nums.length;i++){
nums[i]=r.nextInt(100);
}
System.out.println(Arrays.stream(nums).sum());
}
//没有返回值
/*static class AddTask extends RecursiveAction{
int start,end;
AddTask(int s,int e){
start=s;
end=e;
}
@Override
protected void compute() {
if(end-start<=MAX_NUM){
long sum=0L;
for(int i=start;i<end;i++) sum+=nums[i];
System.out.println("form" + start + " to:"+end +" = " + sum);
}else{
int middle=start+(end-start)/2;
AddTask subTask1=new AddTask(start,middle);
AddTask subTask2=new AddTask(middle,end);
subTask1.fork();
subTask2.fork();
}
}
}*/
//有返回值
static class AddTask extends RecursiveTask<Long> {
int start,end;
AddTask(int s,int e){
start=s;
end=e;
}
@Override
protected Long compute() {
if(end-start<=MAX_NUM){
long sum=0L;
for(int i=start;i<end;i++)sum+=nums[i];
return sum;
}
int middle=start+(end-start)/2;
AddTask subTask1=new AddTask(start,middle);
AddTask subTask2=new AddTask(middle,end);
subTask1.fork();
subTask2.fork();
return subTask1.join()+subTask2.join();
}
}
public static void main(String[] args) throws IOException {
ForkJoinPool fjp=new ForkJoinPool();
AddTask task=new AddTask(0,nums.length);
fjp.execute(task);
long result=task.join();
System.out.println(result);
System.in.read();
}
可以看一下每个线程池的底层,前四个返回的都是ThreadPoolExecutor对象,加了特定的参数就有了自己的特点和特定的适用场景。第五个WorkStealingPool返回的是ForkJoinPool,而ForkJoinPool是AbstractExecutorService的一个实现类,是一个特殊一点的线程池。
殊途同归,所有这些都是为了更好的实现其业务。如果不够用还可以自定义线程池的呀。