Executors-四种创建线程的手段

1 Executors.newCachedThreadPool()

从构造方法可以看出,它创建了一个可缓存的线程池。当有新的任务提交时,有空闲线程则直接处理任务,没有空闲线程则创建新的线程处理任务,队列中不储存任务。线程池不对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。如果线程空闲时间超过了60秒就会被回收。在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统OOM。

package com.zs.thread;

public class TestVolatile {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {
            final int index = i;
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        SimpleDateFormat sdf = new SimpleDateFormat(
                                "HH:mm:ss");
                        System.out.println("运行时间: " +
                                sdf.format(new Date()) + " " + index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        cachedThreadPool.shutdown();
    }
}

因为这种线程有新的任务提交,就会创建新的线程(线程池中没有空闲线程时),不需要等待,所以提交的5个任务的运行时间是一样的。
在这里插入图片描述

package com.thread.excutor;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class FourThreadPoolTest {
    public static void main(String[] args) {
        newCachedThreadPool();
    }

    /**
     *     public static ExecutorService newCachedThreadPool() {
     *         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
     *                                       60L, TimeUnit.SECONDS,
     *                                       new SynchronousQueue<Runnable>());
     *     }
     *
     *  这些池通常会提高执行许多短期异步任务的程序的性能。
     *
     *  SynchronousQueue<Runnable>():
     *     1、SynchronousQueue是BlockingQueue的一种,所以SynchronousQueue是线程安全的。
     *     2、SynchronousQueue 是一个没有数据缓冲的BlockingQueue,容量为0,它不会为队列中元素维护存储空间,它只是多个线程之间数据交换的媒介。
     *     生产者线程对其的插入操作put必须等待消费者的移除操作take。
     *     3、SynchronousQueue非常适合传递性场景做交换工作,生产者的线程和消费者的线程同步传递某些信息、事件或者任务。
     *       SynchronousQueue的一个使用场景是在线程池里。如果我们不确定来自生产者请求数量,但是这些请求需要很快的处理掉,
     *       那么配合SynchronousQueue为每个生产者请求分配一个消费线程是处理效率最高的办法。
     *       Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,
     *       如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
     *     4、SynchronousQueue 最大的特点在于,它的容量为0,没有一个地方来暂存元素,导致每次取数据都要先阻塞,直到有数据被放入;
     *       同理,每次放数据的时候也会阻塞,直到有消费者来取。
     *     5、SynchronousQueue 的容量不是 1 而是 0,因为 SynchronousQueue 不需要去持有元素,它所做的就是直接传递(direct handoff)。
     *       由于每当需要传递的时候,SynchronousQueue 会把元素直接从生产者传给消费者,在此期间并不需要做存储,所以如果运用得当,它的效率是很高的。
     *       使用的数据结构是链表。使用CAS+自旋(无锁),自旋了一定次数后调用 LockSupport.park()进行阻塞。
     *
     *
     */
    private static void newCachedThreadPool() {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        System.out.println(executorService.getActiveCount());
        // sout -> 0
        IntStream.range(0, 5).boxed().forEach(item ->
                executorService.execute(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " [" + item + "]");
                    /**
                     * pool-1-thread-3 [2]
                     * pool-1-thread-4 [3]
                     * pool-1-thread-1 [0]
                     * pool-1-thread-5 [4]
                     * pool-1-thread-2 [1]
                     */
                }));
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(executorService.getActiveCount()); // sout -> 5
    }
    /**
     * 60s之后线程池停止
     * 0
     * 5
     * pool-1-thread-3 [2]
     * pool-1-thread-4 [3]
     * pool-1-thread-1 [0]
     * pool-1-thread-5 [4]
     * pool-1-thread-2 [1]
     *
     * Process finished with exit code 0
     */
}

2 Executors.newFixedThreadPool(10)

从构造方法可以看出,它创建了一个固定大小的线程池,每次提交一个任务就创建一个线程,直到线程数达到线程池的最大值nThreads。线程池的大小一旦达到最大值后,再有新的任务提交时则放入阻塞队列中,等到有线程空闲时,再从队列中取出任务继续执行。FixedThreadPool提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

package com.zs.thread;
public class TestVolatile {
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                        System.out.println("运行时间: " + sdf.format(new Date()) + " " + index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        fixedThreadPool.shutdown();
    }
}

例中创建了一个固定大小为3的线程池,然后在线程池提交了5个任务。在提交第4个任务时,因为线程池的大小已经达到了3并且前3个任务在运行中,所以第4个任务被放入了队列,等待有空闲的线程时再被运行。运行结果如下(注意前3个任务和后2个任务的运行时间):
在这里插入图片描述

/**
     *     public static ExecutorService newFixedThreadPool(int nThreads) {
     *         return new ThreadPoolExecutor(nThreads, nThreads,
     *                                       0L, TimeUnit.MILLISECONDS,
     *                                       // Creates a LinkedBlockingQueue with a capacity of Integer.MAX_VALUE.
     *                                       new LinkedBlockingQueue<Runnable>());
     *     }
     */
    private static void newFixedThreadPool() {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        IntStream.range(0, 20).boxed().forEach(item ->
                executorService.execute(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " [" + item + "]");
                }));
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(executorService.getActiveCount());
    }
10
pool-1-thread-10 [9]
pool-1-thread-1 [0]
pool-1-thread-4 [3]
pool-1-thread-5 [4]
pool-1-thread-9 [8]
pool-1-thread-6 [5]
pool-1-thread-3 [2]
pool-1-thread-8 [7]
pool-1-thread-2 [1]
pool-1-thread-7 [6]
10s之后。。。
pool-1-thread-9 [12]
pool-1-thread-4 [11]
pool-1-thread-6 [13]
pool-1-thread-7 [17]
pool-1-thread-5 [18]
pool-1-thread-2 [16]
pool-1-thread-8 [15]
pool-1-thread-1 [10]
pool-1-thread-3 [14]
pool-1-thread-10 [19]

程序不会停止。。。

3 Executors.newSingleThreadExecutor()

从构造方法可以看出,它创建了一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

package com.zs.thread;

public class TestVolatile {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        SimpleDateFormat sdf = new SimpleDateFormat(
                                "HH:mm:ss");
                        System.out.println("运行时间: " +
                                sdf.format(new Date()) + " " + index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        singleThreadExecutor.shutdown();
    }
}

因为该线程池类似于单线程执行,所以先执行完前一个任务后,再顺序执行下一个任务:
在这里插入图片描述
既然类似于单线程执行,那么这种线程池还有存在的必要吗?这里的单线程执行指的是线程池内部,从线程池外的角度看,主线程在提交任务到线程池时并没有阻塞,仍然是异步的。

/**
     *     public static ExecutorService newSingleThreadExecutor() {
     *         return new FinalizableDelegatedExecutorService
     *             (new ThreadPoolExecutor(1, 1,
     *                                     0L, TimeUnit.MILLISECONDS,
     *                                     new LinkedBlockingQueue<Runnable>()));
     *     }
     *
     *
     */
    private static void newSingleThreadExecutor() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        IntStream.range(0, 8).boxed().forEach(item ->
                executorService.execute(() -> {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " [" + item + "]" + System.currentTimeMillis());
                }));
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
pool-1-thread-1 [0]1669445965474
pool-1-thread-1 [1]1669445967480
pool-1-thread-1 [2]1669445969481
pool-1-thread-1 [3]1669445971487
pool-1-thread-1 [4]1669445973492
pool-1-thread-1 [5]1669445975497
pool-1-thread-1 [6]1669445977500
pool-1-thread-1 [7]1669445979505

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

不会停止运行,始终有一个线程在运行。

4 Executors.newScheduledThreadPool()

这个方法创建了一个固定大小的线程池,支持定时及周期性任务执行。
首先看一下定时执行的例子:

package com.zs.thread;

public class TestVolatile {
    public static void main(String[] args) {
        final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        System.out.println("提交时间: " + sdf.format(new Date()));
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("运行时间: " + sdf.format(new Date()));
            }
        }, 3, TimeUnit.SECONDS);
        scheduledThreadPool.shutdown();
    }
}

使用该线程池的schedule方法,延迟3秒钟后执行任务,运行结果如下:
在这里插入图片描述

package com.zs.thread;

public class TestVolatile {
    public static void main(String[] args) throws InterruptedException {
        final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        System.out.println("提交时间: " + sdf.format(new Date()));
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("运行时间: " + sdf.format(new Date()));
            }
        }, 1, 3, TimeUnit.SECONDS);
        Thread.sleep(10000);
        scheduledThreadPool.shutdown();
    }
}

使用该线程池的scheduleAtFixedRate方法,延迟1秒钟后每隔3秒执行一次任务,运行结果如下:
在这里插入图片描述

5 Executors.newWorkStealingPool()

 /**
     *     public static ExecutorService newWorkStealingPool() {
     *         return new ForkJoinPool
     *             (Runtime.getRuntime().availableProcessors(),
     *              ForkJoinPool.defaultForkJoinWorkerThreadFactory,
     *              null, true);
     *     }
     *
     *     可以传入线程的数量,不传入则默认使用当前计算机中可用的cpu数量,能够合理的使用CPU进行对任务操作(并行操作)。
     *     适合使用在很耗时的任务中,底层用的ForkJoinPool 来实现的:
     *     ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”分发到不同的cpu核心上执行,执行完后再把结果收集到一起返回。
     */
    private static void newWorkStealingPool() {
        /*Optional.of(Runtime.getRuntime().availableProcessors())
                .ifPresent(System.out::println);*/ // 8核CPU
        SimpleDateFormat sdf = new SimpleDateFormat(
                "HH:mm:ss");
        ExecutorService executorService = Executors.newWorkStealingPool();
        List<Callable<String>> callableList = IntStream.range(0, 20).boxed().map(item ->
                (Callable<String>) () -> {
                    System.out.println("Thread - " + Thread.currentThread().getName() + " - " + sdf.format(new Date()));
                    sleep(2L);
                    return "task-" + item;
                }).collect(Collectors.toList());
        try {
          // invokeAll的作用是:等待所有的任务执行完成后统一返回。
          // 这里与大家分享的是:如果executorService是公共线程池慎用,如果这时候有另外一个请求也不断地往线程池里扔任务,这时候这个请求是不是就一直不停的阻塞了。
            executorService.invokeAll(callableList).stream().map(item -> {
                try {
                    return item.get();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }).forEach(System.out::println);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

线程会执行结束!

Thread - ForkJoinPool-1-worker-3 - 15:29:15
Thread - ForkJoinPool-1-worker-1 - 15:29:15
Thread - ForkJoinPool-1-worker-6 - 15:29:15
Thread - ForkJoinPool-1-worker-2 - 15:29:15
Thread - ForkJoinPool-1-worker-4 - 15:29:15
Thread - ForkJoinPool-1-worker-5 - 15:29:15
Thread - ForkJoinPool-1-worker-0 - 15:29:15
Thread - ForkJoinPool-1-worker-7 - 15:29:15
Thread - ForkJoinPool-1-worker-3 - 15:29:17
Thread - ForkJoinPool-1-worker-4 - 15:29:17
Thread - ForkJoinPool-1-worker-0 - 15:29:17
Thread - ForkJoinPool-1-worker-5 - 15:29:17
Thread - ForkJoinPool-1-worker-6 - 15:29:17
Thread - ForkJoinPool-1-worker-7 - 15:29:17
Thread - ForkJoinPool-1-worker-2 - 15:29:17
Thread - ForkJoinPool-1-worker-1 - 15:29:17
Thread - ForkJoinPool-1-worker-0 - 15:29:19
Thread - ForkJoinPool-1-worker-4 - 15:29:19
Thread - ForkJoinPool-1-worker-5 - 15:29:19
Thread - ForkJoinPool-1-worker-1 - 15:29:19
task-0
task-1
task-2
task-3
task-4
task-5
task-6
task-7
task-8
task-9
task-10
task-11
task-12
task-13
task-14
task-15
task-16
task-17
task-18
task-19

Process finished with exit code 0
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值