【并发编程】-Java线程池

在开发过程中经常会遇到并发量比较高的代码,可以用多线程去解决的。除了我们常用的自定义线程池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的一个实现类,是一个特殊一点的线程池。

殊途同归,所有这些都是为了更好的实现其业务。如果不够用还可以自定义线程池的呀。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值