JUC---多线程下控制并发流程(JDK13)

10 篇文章 0 订阅

java.util.concurrent包系列文章
JUC—ThreadLocal源码解析(JDK13)
JUC—ThreadPoolExecutor线程池源码解析(JDK13)
JUC—各种锁(JDK13)
JUC—原子类Atomic*.java源码解析(JDK13)
JUC—CAS源码解析(JDK13)
JUC—ConcurrentHashMap源码解析(JDK13)
JUC—CopyOnWriteArrayList源码解析(JDK13)
JUC—并发队列源码解析(JDK13)
JUC—多线程下控制并发流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


一、概览

控制并发流程的工具类概览

在这里插入图片描述


二、CountDownLatch

倒数计数器,倒数结束之前,一直处在等待状态,直到计时结束,此线程才能继续工作

唯一的构造方法

// 传入倒数计数数量
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

await方法。调用await的线程会被挂起,直到count的数量为0才会继续执行

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

倒数的方法countDown,调用1次count减1。直到count为0,等待的线程会被唤醒。

public void countDown() {
    sync.releaseShared(1);
}

使用示例
1、一等多:一个线程等待其他线程完成工作之后继续执行,这里演示主线程等待子线程执行完毕再继续执行。
譬如拼团网购,需要5个人一起拼,只有凑齐5个之后才能拼团成功。

public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(5);
    ExecutorService service = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 5; i++) {
        final int no = i + 1;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (Math.random() * 10000));
                    System.out.println("用户No." + Thread.currentThread().getId() + "加入了拼团");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }
        };
        service.submit(runnable);
    }
    System.out.println("等待拼团用户达到5个");
    latch.await();
    System.out.println("凑满5个人拼团啦,可以生成订单了");
}
执行结果:
等待拼团用户达到5个
用户No.14加入了拼团
用户No.18加入了拼团
用户No.15加入了拼团
用户No.16加入了拼团
用户No.17加入了拼团
凑满5个人拼团啦,可以生成订单了

2、多等一:多个线程等待某一个线程完成工作之后继续执行。
譬如开车的时候,路上等红灯。有4个车道,大家都在等待绿灯亮起,大家一起出发。

 public static void main(String[] args) throws InterruptedException {
    CountDownLatch begin = new CountDownLatch(1);
    ExecutorService service = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 5; i++) {
        final int no = i + 1;
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("No." + no + "在等红灯,等待绿灯亮起");
                try {
                    begin.await();
                    System.out.println("No." + no + "开始踩油门加速了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        service.submit(runnable);
    }
    Thread.sleep(5000);
    System.out.println("红灯亮起,大家可以开车了!");
    begin.countDown();
}
执行结果:
No.2在等红灯,等待绿灯亮起
No.5在等红灯,等待绿灯亮起
No.4在等红灯,等待绿灯亮起
No.3在等红灯,等待绿灯亮起
No.1在等红灯,等待绿灯亮起
红灯亮起,大家可以开车了!
No.2开始踩油门加速了
No.1开始踩油门加速了
No.3开始踩油门加速了
No.4开始踩油门加速了
No.5开始踩油门加速了

注意:不能重用,想重用可以新建一个CountDownLatch实例或者使用CyclicBarrier


三、Semaphore

信号量,限制有限资源的使用量。相当于许可证。数量有限的。拿到许可证就可以执行。
譬如耗时的服务和资源,限制同一时刻并发请求的数量。

在这里插入图片描述
假如信号量设置为3,线程1-2-3获得了信号量许可在请求服务,线程4没有获得信号量,就阻塞等待,线程1-2-3执行请求完毕,执行release()方法释放一个信号量之后,线程4就可以或者执行请求服务的机会。
在这里插入图片描述

构造方法

// 可以选择公平和非公平策略,公平策略会优先考虑等待时间最长的线程,新加入的线程排在队尾
// 非公平策略允许新来的线程在一定条件下插队
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

其他方法

  • acquire(int permits),获取信号量,不响应中断,permits=想要获取的信号量数量
  • acquireUninterruptibly(int permits),获取信号量,会响应中断
  • tryAcquire(int permits),尝试获取信号量,没有的话不阻塞和tryLock很相似
  • tryAcquire(int permits, long timeout, TimeUnit unit),带超时参数的tryAcquire
  • release(int permits),释放信号量,必须释放

使用示例

 static Semaphore semaphore = new Semaphore(5, true);
 public static void main(String[] args) {
     ExecutorService service = Executors.newFixedThreadPool(50);
     for (int i = 0; i < 5; i++) {
         service.submit(new Task());
     }
     service.shutdown();
 }

 static class Task implements Runnable {
     @Override
     public void run() {
         try {
             semaphore.acquire(3);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + "拿到了许可证");
         try {
             Thread.sleep(200);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + "释放了许可证");
         // 可以选择释放指定数量的信号量,不过获取和释放的数量最好一致
         semaphore.release(3);
     }
 }
执行结果:
pool-1-thread-2拿到了许可证
pool-1-thread-2释放了许可证
pool-1-thread-1拿到了许可证
pool-1-thread-1释放了许可证
pool-1-thread-5拿到了许可证
pool-1-thread-5释放了许可证
pool-1-thread-4拿到了许可证
pool-1-thread-4释放了许可证
pool-1-thread-3拿到了许可证
pool-1-thread-3释放了许可证

四、Condition

当线程1要等待某个条件的时候就去执行condition.await(),就进入阻塞状态,等待被唤醒。
这个时候线程2去执行了这个条件,调用condition.single()或者singleAll()方法,JVM就会从被阻塞的线程里找到那些等待这个condition的线程,线程1收到可执行信号的时候,它的线程状态就会变成可执行状态。
single()唤醒等待时间最长的那个线程,singleAll()唤醒所有等待线程。

在这里插入图片描述
使用示例,实现生产者消费者模式

public class ConditionDemo {

    private int queueSize = 10;
    private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
    private Lock lock = new ReentrantLock();
    // 没有满的Condition
    private Condition notFull = lock.newCondition();
    // 没有空的Condition
    private Condition notEmpty = lock.newCondition();

    public static void main(String[] args) {
        ConditionDemo2 conditionDemo2 = new ConditionDemo2();
        Producer producer = conditionDemo2.new Producer();
        Consumer consumer = conditionDemo2.new Consumer();
        producer.start();
        consumer.start();
    }

    class Consumer extends Thread {

        @Override
        public void run() {
            consume();
        }

        private void consume() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == 0) {
                        System.out.println("队列空,等待数据");
                        try {
                            notEmpty.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.poll();
                    notFull.signalAll();
                    System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    class Producer extends Thread {

        @Override
        public void run() {
            produce();
        }

        private void produce() {
            while (true) {
                lock.lock();
                try {
                    while (queue.size() == queueSize) {
                        System.out.println("队列满,等待有空余");
                        try {
                            notFull.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    queue.offer(1);
                    notEmpty.signalAll();
                    System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

Condition是Object.wait()的升级版。如果说Lock用来代替synchronized的,Condition就是用来代替Object.wait()和Object.notify()的。
调用await之前必须手动持有锁,否则抛异常。调用await方法会自动释放锁,与Object.wait()一样。


五、CyclicBarrier

CyclicBarrier循环栅栏,类似于CountDownLatch。

它们的不同点

  • CyclicBarrier要等固定数量的线程到达了栅栏位置才能继续执行。CountDownLatch只需要等待倒数技术为0,CountDownLatch用于事件,CyclicBarrier用于线程。
  • CyclicBarrier是可以重用的。

使用示例

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有人都到场了, 大家统一出发!");
            }
        });
        for (int i = 0; i < 6; i++) {
            new Thread(new Task(i, cyclicBarrier)).start();
        }
    }

    static class Task implements Runnable{
        private int id;
        private CyclicBarrier cyclicBarrier;

        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程" + id + "现在前往集合地点");
            try {
                Thread.sleep((long) (Math.random()*10000));
                System.out.println("线程"+id+"到了集合地点,开始等待其他人到达");
                cyclicBarrier.await();
                System.out.println("线程"+id+"出发了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}
执行结果:
线程3现在前往集合地点
线程1现在前往集合地点
线程5现在前往集合地点
线程0现在前往集合地点
线程2现在前往集合地点
线程4现在前往集合地点
线程0到了集合地点,开始等待其他人到达
线程4到了集合地点,开始等待其他人到达
所有人都到场了, 大家统一出发!
线程4出发了
线程0出发了
线程5到了集合地点,开始等待其他人到达
线程3到了集合地点,开始等待其他人到达
所有人都到场了, 大家统一出发!
线程3出发了
线程5出发了
线程2到了集合地点,开始等待其他人到达
线程1到了集合地点,开始等待其他人到达
所有人都到场了, 大家统一出发!
线程1出发了
线程2出发了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值