四、并发包工具与线程池

1.并发包

JUC包常用工具类图

常用工具
JUC包全景类图

在这里插入图片描述

1.1 计数器:CountDownLatch

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值为0时,表示所有的线程已经完成了任务,然后在闭锁等待的线程就可以恢复执行任务。

main():
    CountDownLatch countDownLatch = new CountDownLatch(2);
    new Thread(new Runnable(){
        @override run():
            sysout(Thread.currentThread().getName + ",子线程执行...");
            sysout(Thread.currentThread().getName + ",子线程执行完毕...");
            countDownLatch.countDown();
    }).start();

    new Thread(new Runnable() {
        @override run():
           sysout(Thread.currentThread().getName + ",子线程执行...");
            sysout(Thread.currentThread().getName + ",子线程执行完毕...");
            countDownLatch.countDown();
    }).start();
    countDownLatch.await();
    sysout("两个子线程执行完毕,主线程执行");

1.2 CyclicBarrier屏障

CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待的线程被唤醒并继续。

CyclicBarrier初始时还可带一个Runnable参数,此Runnable任务在CyclicBarrier的数目达到后,所有其他线程被唤醒前并执行。

public class Writer extends Thread {
    private CyclicBarrier cyclicBarrier;

    public Writer(CyclicBarrier cyclicBarrier){
        this.cyclicBarrier = cyclicBarrier;
    }

    @override
    public void run(){
        sysout("线程" + Thread.currentThread().getName() + ",正在写入数据");
        try:Thread.sleep(3000);
        sysout("线程" + Thread.currentThread().getName() + ",写入数据成功");
        try:cyclicBarrier.await();
        sysout("所有线程执行完毕..."); //最后执行五次
    }
}

main():
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
    for(int i = 0; i < 5; i++){
        Writer writer = new Writer(cyclicBarrier);
        writer.start();
    }

1.3 计数信号量 Semaphore

Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后,归还,超过阈值后,线程申请许可信号将会被阻塞。

基本示例:

public class SemaphoreTest{
    public static void main(String[] args){
        ExecutorService service = Executors.newCachedThreadPool();
        // 最大并发数为3
        final Semaphore sp = new Semaphore(3);
        for(int index = 0; index < 10; index++){
            // 记录第几个任务
            final int no = index;

            Runnable run = new Runnable(){
                @override
                public void run(){
                    // 获取许可
                    try:sp.acquire();
                        sysout(Thread.currentThread().getName() + "获取许可" + no + "剩余:" + sp.avaliablePermits());
                        Thread.sleep();
                        // 访问后释放
                        sp.release();
                        sysout(Thread.currentThread().getName()+ "释放许可" + no + "剩余:" + sp.avaliablePermits());
                    catch:
                }
            }
            service.execute(run);
        }
        // 关闭线程池
        service.shutdown();
    }
}

结果:初始化:当前可用3个并发
pool-1-thread-1获取许可(0):剩余1
pool-1-thread-3获取许可(2):剩余0
pool-1-thread-2获取许可(1):剩余1

从结果中可以看出,Semaphore 并没有实现对共有数据的同步,在操作公共数据的时候,需要我们自己实现。

availablePermits():获取当前可用的资源数量
acquire():申请资源
release():释放资源

2 阻塞队列 BlockingQueue

2.1 介绍

BlockingQueue是一个接口,继承自Queue,所以其实现类也可以作为Queue来使用,而Queue又继承自Collection接口。

阻塞队列,顾名思义,首先它是一个队列。

队列:先进先出

经典的"生产者"和"消费者"模型中,生产者和消费者的处理速度,肯定是不完全匹配的。因此我们需要引入阻塞的概念:如果生产过剩,那就暂停一下等到消费者消费,反之亦然。

然而在concurrent包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节(比如Lock机制),尤其还要兼顾效率和线程安全。

BlockingQueue,我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程。

BlockingQueue,对插入操作、移除操作、获取元素操作提供了四种不同的方法用于不同的场景中使用:

1)抛出异常:如果试图的操作无法立即执行,抛一个异常。
2)返回特殊值:(null或true、false,取决于具体操作)
3)阻塞等待此操作:直到这个操作成功。
4)超时:直到成功,或者超时指定时间。

抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()polltake()poll(e,time,unit)
检查element()peek()不可用不可用

放入数据:

  • offer(anObject):表示如果可能的话,将anObject加入到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。(本方法不阻塞当前执行方法的线程)。

  • put(anObject):把anObject加到BlockingQueue里,如果BlockingQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续。

获取数据:

  • poll(time): 取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数指定的时间,取不到时返回null。
  • take(): 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,则阻断,进入等待状态,直到BlockingQueue有新的数据被加入。
  • drainTo(): 一次从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数)

BlockingQueue是设计用来实现生产者-消费者队列的,当然,你也可以将它当做普通的Collection来用,但是这类操作通常并不高效。

BlockingQueue常见的5种实现:
BlockingQueue:
ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue。

2.2 ArrayBlockingQueue

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象。ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此意味着,两者无法真正并行运行。因此并发效率相对较低,这点尤其不同于LinkedBlockingQueue.

通过源码分析,其实ArrayBlockingQueue完全是可以使用分离锁的,但是作者Doug Lea并没有这么干,理由如下:

ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,在性能上完全占不到便宜。

ArrayBlockingQueue和LinkedBlockingQueue还有一个明显的不同之处,前者在插入或删除元素时不会产生和销毁任何额外的对象实例,而后者则会产生一个额外的Node对象。

ArrayBlockingQueue,我们可以在构造的时候指定以下三个参数:
public ArrayBlockingQueue(int capacity, boolean fair, Collection c);

  • 1.队列容量,其限制了队列中最多允许的元素个数。
  • 2.指定独占锁是公平锁还是非公平锁,非公平锁的吞吐量比较高,公平锁可以保证每次都是等待最久的线程获取到锁。
  • 3.可以指定用一个集合来初始化,将此集合中的元素在构造方法期间就先添加到队列中。

示例:

public class Cookie{
    private String number;
    public Cookie(String number){
        this.number = number;
    }
    @override
    public String toString(){
        return number + "";
    }
}
  • 生产者
class Produce extends Thread{
    private static int i = 0;
    private ArrayBlockingQueue<Cookie> arrayBlockingQueue;

    public Produce(ArrayBlockingQueue<Cookie> arrayBlockingQueue){
        this.arrayBlockingQueue = arrayBlockingQueue;
    }

    @override
    public void run(){
        try:
            while(i < 1000){
                arrayBlockingQueue.put(new Cookie("cookie" + i));
                // 每生产100个,休息10s
                if(++i % 100 == 0){
                    Thread.sleep(10 * 1000);
                }
            }
    }
}
  • 消费者
public class consume implements Runnable{
    private ArrayBlockingQueue<Cookie> arrayBlockingQueue;
    public Consume(ArrayBlockingQueue<Cookie> arrayBlockingQueue){
        this.arrayBlockingQueue = arrayBlockingQueue;
    }
    @override
    public void run(){
        try:
            while(true){
                Cookie poll = arrayBlockingQueue.poll(5, TimeUnit.SECONDS);
                if(poll != null){
                    sysout(Thread.currentThread().getName + "--consume--" + poll);
                }
            }
    }
}
  • 测试
ArrayBlockingQueue<Cookie> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
// 开启一个生产者
for(int i = 0; i < 1; i++){
    new Produce(arrayBlockingQueue).start();
}
// 5个消费者
for(int i = 0; i < 5; i++){
    new Thread(new Consume(arrayBlockingQueue)).start();
}

2.2 LinkedBlockingQueue

基于链表的阻塞队列,LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步。

需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能被消耗殆尽了。

ArrayBlockingQueue和LinkedBlockingQueue是最普遍最常用的阻塞队列。

2.3 DelayQueue

DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。

DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

DelayQueue阻塞队列在我们系统开发中也常常会用到,例如:缓存系统的设计,缓存中的对象,超过了空闲时间,需要从缓存中移出;

任务调度系统,能够准确的把握任务的执行时间,我们可能需要通过线程处理很多时间上要求很严格的数据。

DelayQueue中的所有元素必须实现Delayed接口,getDelay方法用于返回对象的剩余有效时间,实现Comparable接口是为了比较两个对象,以便排序。

  • getDelay方法返回元素的剩余有效时间,只有当该对象的getDelay方法返回的剩余时间<=0时才会出队,可以根据入参的TimeUnit选择时间的表示形式(秒、微妙、纳秒等),一般选择纳秒以提高精度。

  • compareTo方法用于比较两个元素的大小,以便在队列中排序,我们定义的规定先失效的元素将出队,所以先实效的元素应该在堆顶,即compareTo方法返回结果<0的元素优先出队。

2.3.1 业务场景一:多考生考试

模拟一个考试的日子,考试时间:120分钟,30分钟后方可交卷,当时间到了,或者学生都交完卷了宣布考试结束。

实现思想:用DelayQueue存储考生类(Student),每一个考生都有自己的名字和完成考卷的时间,Teacher线程对DelayQueue进行监控,收获完成试卷小于120分钟的学生试卷,当考试时间120分钟到时,先关闭Teacher线程,然后强制DelayQueue中还存在的考生交卷。

  • 学生类
public class Student implements Runnable, Delayed{
    private String name;
    // 希望用时
    private Long workTime;
    // 交卷时间
    private Long submitTime;
    // 是否强制交卷
    private boolean isForce = fale;

    private CountDownLatch countDownLatch;

    public Student(String name, Long workTime, CountDownLatch countDownLatch){
        this.submitTime = TimeUnit.NANOSECONDS.convert(workTime, TimeUnit.NANOSECONDS)
        + System.nanoTime();
    }

    @override
    public Integer compareTo(Delayed o){
        if(o == null || !(o instanceof Student)){
            return 1;
        }
        if(o == this){
            return 0;
        }
        Student s = (Student)o;
        if(this.workTime > s.workTime){
            return 1;
        }else if(this.workTime == s.workTime){
            return 0;
        }else{
            return -1;
        }
    }

    @override
    public Long getDelay(TimeUnit unit){
        return unit.convert(submitTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }

    @override
    public void run(){
        if(isForce){
            sysout(name + "交卷,希望用时:") + workTime + "分钟" + ",实际用时120分钟";
        }else{
            sysout(name + "交卷,希望用时:") + workTime + "分钟" + ",实际用时" +;
        }
        countDownLatch.countDown();
    }
}
  • 教师类
public class Teacher implements Runnable{
    private DelayQueue<Student> students;

    public Teacher(DelayQueue<Student> students){
        this.students = students;
    }

    @override
    public void run(){
        try: sysout("test start");
            while(!Thread.interrupt()){
                Student.take().run();
                // 此处需要注意,take是阻塞的,只有时间到了,才会take出来,执行run方法。
                // 中途有可能会被interrupted,比如强制交卷,就不会take了,需要执行强制交卷的线程任务。
            }
    }
}
  • 结束考试
public class EndExam extends Student{
    private DelayQueue<Student> students;
    private CountDownLatch countDownLatch;
    private Thread teacherThread;

    public EndExam(DelayQueue<Student> students,WorkTime workTime, CountDownLatch countDownLatch){
        super("强制收卷", workTime, countDownLatch);
    }

    @override
    public void run(){
        teacherThread.interrupt();

        Student tmpStudent;
        for(Iterator<Student> iterator2 = Students.iterator(); iterator2.hasNext();){
            tmpStudent = iterator2.next();
            tmpStudent.setForce(true);
            tmpStudent.run();
        }
        countDownLatch.countDown();
    }
}
  • 考试过程
public class Exam{
    public static void main(String[] args){
        int studentNumber = 20;
        CountDownLatch countDownLatch = new CountDonwLatch(StudentNumber + 1);

        DelayQueue<Student> students = new DelayQueue<>();

        Random random = new Random();
        for(int i = 0; i < studentNumber; i++){
            students.put(new Student("student" + (i + 1), random.nextInt(120) + 30, countDownLatch));
        }
        Thread teacherThread = new Thread(new Teacher(students));

        students.put(new EndExam(students, 120, countDownLatch, teacherThread));

        teacherThread.start();

        countDownLatch.await();

        sysout("考试时间到,全部交卷");
    }
}

2.3.2 业务场景二:具有过期时间的缓存

向缓存添加内容时,给每一个key设定过期时间,系统自动将超过过期时间的key清除。

  • 1.当向缓存中添加key-value对时,如果这个key在缓存中存在并且还没有过期,需要用这个key对应的新过期时间

  • 2.为了能够让DelayQueue将其已保存的key删除,需要重写实现Delayed接口添加到DelayQueue的DelayedItem的hashCode函数和equals函数

  • 3.当缓存关闭,监控程序也应关闭,因而监控线程应当用守护线程

/**
 * 利用延迟队列,来书写一个具有过期key效果的简单缓存  缓存使用ConcurrentHashMap实现
 *
 */
public class Cache<K, V> {

    // 模拟装载缓存数据
    public ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
    // 缓存即将要过期的key们
    public DelayQueue<DelayedItem<K>> queue = new DelayQueue<>();

    public static void main(String[] args) throws InterruptedException {
        Random random = new Random();
        int cacheNumber = 10;
        int liveTime = 0;
        Cache<String, Integer> cache = new Cache<>();

        for (int i = 0; i < cacheNumber; i++) {
            liveTime = random.nextInt(3000);
            System.out.println(i + "  " + liveTime);
            cache.put(i + "", i, random.nextInt(liveTime));

            if (random.nextInt(cacheNumber) > 7) {
                liveTime = random.nextInt(3000);
                System.out.println(i + "  " + liveTime);
                cache.put(i + "", i, random.nextInt(liveTime));
            }
        }

        Thread.sleep(3000);
        System.out.println("--------------");
    }


    /**
     * 向缓存里面添加元素  可以指定key的存活时间
     *
     * @param k
     * @param v
     * @param liveTime
     */
    public void put(K k, V v, long liveTime) {
        V v2 = map.put(k, v);
        DelayedItem<K> tmpItem = new DelayedItem<>(k, liveTime);

        // 把旧的移除掉 若存在旧的话
        if (v2 != null) {
            queue.remove(tmpItem);
        }
        queue.put(tmpItem);
    }

    // 创建缓存对象的时候 开启一个守护线程 一直不停的去检查 阻塞队列里面是否有元素需要过期了移出来
    public Cache() {
        Thread t = new Thread(() -> {
            while (true) {
                DelayedItem<K> delayedItem = queue.poll(); //阻塞
                if (delayedItem != null) {
                    map.remove(delayedItem.getT());
                    System.out.println(System.nanoTime() + " remove " + delayedItem.getT() + " from cache");
                }
                try {
                    Thread.sleep(300);
                } catch (Exception e) {
                }
            }
        });
        t.setDaemon(true); // 一定需要是守护线程
        t.start();
    }

}


class DelayedItem<T> implements Delayed {

    private T t;
    private long liveTime;
    private long removeTime;

    public DelayedItem(T t, long liveTime) {
        this.setT(t);
        this.liveTime = liveTime;
        this.removeTime = TimeUnit.NANOSECONDS.convert(liveTime, TimeUnit.NANOSECONDS) + System.nanoTime();
    }

    @Override
    public int compareTo(Delayed o) {
        if (o == null) return 1;
        if (o == this) return 0;
        if (o instanceof DelayedItem) {
            DelayedItem<T> tmpDelayedItem = (DelayedItem<T>) o;
            if (liveTime > tmpDelayedItem.liveTime) {
                return 1;
            } else if (liveTime == tmpDelayedItem.liveTime) {
                return 0;
            } else {
                return -1;
            }
        }
        // 按照getDelay来比较即可  因为有可能传进俩的对象  并不是DelayedItem对象,而是别的Delayed对象
        long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return diff > 0 ? 1 : diff == 0 ? 0 : -1;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(removeTime - System.nanoTime(), unit);
    }

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    // 必须重写啊  因为需要根据key移除 底层依赖hashCode和equals
    @Override
    public int hashCode() {
        return t.hashCode();
    }

    //必须重写啊  因为需要根据key移除 底层依赖hashCode和equals
    @Override
    public boolean equals(Object object) {
        if (object instanceof DelayedItem) {
            return object.hashCode() == hashCode() ? true : false;
        }
        return false;
    }

}

2.4 优先队列 PriorityBlockingQueue

PriorityBlockingQueue是一个没有边界的队列,所有插入PriorityBlockingQueue的对象必须实现java.lang.Comparable接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的。

示例:优先排队,假设在这么一个场景下,银行开始办理业务之前,已经来了20个客户,而且银行认为谁钱多,谁就优先办理业务。

  • human类,包括姓名和存款两个属性:
public class Human{
    private int money;
    private String name;
    public Human(money, name){
        this.money = money;
        this.name = name;
    }
}

public class HumanComparator implements Comparator<Human>{
    @override
    public int compare(Human o1, Human o2){
        return o2.getMoney() - o1.getMoney();
    }
}
  • 排队的类(生产者)
public class ProducerRunnable implements Runnable{
    private static final String name = "...";
    private Random random = new Random();
    private PriorityBlockingQueue<Human> queue;

    public ProducerRunnable(PriorityBlockingQueue<Human> queue){
        this.queue = queue;
    }

    @override
    public void run(){
        for(int i = 0; i < 20; i++){
            Human human = new Human(random.nextInt(10000), "小" + name.charAt(i));
            queue.put(human);
            sysout(human + "开始排队...");
        }
    }
}
  • 办理业务的类(消费者)
public class ConsumerRunnable implements Runnable{
    private PriorityBlockingQueue<Human> queue;

    public ConsumerRunnable(PriorityBlockingQueue<Human> queue){
        this.queue = queue;
    }

    @override
    public void run(){
        while(true){
            Human take = queue.poll();
            if(take == null){
                break;
            }
            sysout(take + "办理业务");
        }
    }
}
  • 测试
main():
    PriorityBlockingQueue<Human> queue = new PriorityBlockingQueue<Human>(200, new HumanComparator());

    Thread thread = new Thread(new ProducerRunnable(queue));
    thread.start();
    thread.join();

    new Thread(new ConsumerRunnable(queue)).start();

2.5 同步队列:SynchronousQueue

一种无缓冲的等待队列,主要有如下特点:

  • SynchronousQueue 容量为0,无法用于数据的存储。

  • 每一次向队列中执行写操作时,写线程都会等待,直到另一个线程去执行读操作时,写线程才会返回;反之亦然,并且写入的元素不允许为null。

  • 由于 SynchronousQueue 的容量为0,因为peek(),总是返回null;如果是执行迭代操作,同样也是没有任何元素可以迭代。

SynchronousQueue 一般用于生产、消费大致相当的情况下,这样才不会导致系统中过多的过程处于阻塞状态。

示例:

  • 生产者
static class Product extends Thread{
    SynchronousQueue<Integer> queue;

    public Product(SynchronousQueue<Integer> queue){
        this.queue = queue;
    }

    @override
    public void run(){
        while(true){
            int rand = new Random.nextInt(1000);
            sysout("生产了一个产品:" + rand);
            sysout("等待三秒后送出去");
            TimuUnit.SECONDS.sleep(3);
            queue.offer(rand);
            sysout("产品生产完成:" + rand);
        }
    }
}
  • 消费者
static class Customer extends Thread{
    SynchronousQueue<Integer> queue;

    public Customer(SynchronousQueue<Integer> queue){
        this.queue = queue;
    }

    @override
    public void run(){
        while(true){
            try:sysout("消费了一个产品:" + queue.take());
                sysout("-----");
        }
    }
}
  • 主函数
main():
    SynchronousQueue<Integer> queue = new SynchronousQueue<Integer> queue();
    new Customer(queue).start();
    new Product(queue).start();

3 阻塞队列

在并发编程中,实现一个线程安全的队列有两种方式:一种使用阻塞算法,另一种使用非阻塞算法:
非阻塞队列:
ConcurrentLinkedQueue:基于链接节点的无界线程安全队列
LinkedList:非线程安全,双向链表

在并发编程中,一般推荐使用阻塞队列,这样可以实现尽量避免程序出现意外的错误。

4 线程池

线程池的优点:

  • 1.降低资源消耗,通过重复利用已创建的线程,降低创建和销毁线程造成的系统资源消耗。
  • 2.提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
  • 3.提高线程的可管理性:线程是稀缺资源,如果过多的创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一分配,调优和监控。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executro并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService.

比较重要的几个类:

  • 1.ExecutorService:真正的线程池接口。
  • 2.SheculeExecutorService:解决那些需要任务重复执行的问题。
  • 3.ThreadPoolExecutor:Executor的默认实现。
  • 4.ScheduledThreadPoolExecutor:继承ThreadPoolExecutor的SheduledExecutorSerivce的接口实现,周期性任务调度的类实现。

Java通过Executors提供4中线程池,分别为:

  • .newCachedThreadPool
  • .newFixedThreadPool
  • .newScheduledThreadPool
  • .newSingleThreadExecutor

4.1 newCachedThreadPool

创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空间(60s不执行任务的线程),当任务数增加时,此线程又可以智能的添加新线程来处理任务。
此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

main():
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for(int i = 0; i < 10; i++){
        final int index = i;
        try:Thread.sleep(index * 1000);
        cachedThreadPool.execute(new Runnable(){
            @override run():
                sysout("执行:" + index + ",线程名称:" + thredName);
        })
    }

4.2 newFixedThreadPool

创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

main():
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

    for(int i = 0; i < 10; i++){
        final int index = i;

        fixedThreadPool.execute(new Runnable()) {
            @override run():
                try:
                    sysout("执行:" + index + ",线程名称:" + thredName);
        }
    }

4.3 newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行、延迟执行。

main():
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

    scheduledThreadPool.schedule(new Runnable(){
        @override run():
            sysout("延迟3秒执行");
    }, 3, TimeUnit.SECONDS);

    scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
        @override run():
            sysout("延迟1秒后每3秒执行一次");
    }, 3, TimeUnit.SECONDS);

4.4 new SingleThreadExecutor:

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i = 0; i < 10; i++){
    final int index = i;
    singleThreadExecutor.execute(new Runnable(){
        @override run():
            sysout("执行:" + index + ",线程名称:" + thredName);
    })
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值