JUC基础总结03 - 并发包常用类

JUC基础总结03 - 并发包常用类

    1.CountDownLatch
    2.CyclicBarrier
    3.Semaphore
    
一、CountDownLatch(递减计数器)
使用之前:
    主线程发起线程,无法知道子线程是否执行完
使用之后:
    主线程发起线程,并使用await等待,每个子线程完成操作,CountDownLatch 计数器-1,直到0,主线程被唤起继续工作
主要两个方法:
    1) 子线程(组)调 countDown 方法
    2) 主线程调 await 方法进入阻塞,等待计数 = 0 被唤醒,再继续执行

1.简单递减计数示例

public static void main(String[] args) {
    CountDownLatch latch = new CountDownLatch(6);
    for (int i = 0; i < 6; i++) {
        final int tempInt = i;
        new Thread(() -> {
            System.out.println("线程" + tempInt + "操作sth完成");
            latch.countDown();
        }, String.valueOf(i)).start();
    }
    try {
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("所有线程都完成操作,主线程结束");
}

执行结果:

线程0操作sth完成
线程4操作sth完成
线程3操作sth完成
线程2操作sth完成
线程1操作sth完成
线程5操作sth完成
所有线程都完成操作,主线程结束

2.CountDownLatch 源码分析
    1) CountDownLatch 的数据结构中,包含一个 Sync 对象,Sync对象继承了AQS
    2) CountDownLatch 是通过“共享锁”实现的

package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
 
public class CountDownLatch {
    /**
     * 计数锁存器:使用AQS的state表示计数
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
 
        Sync(int count) {
            setState(count);
        }
 
        int getCount() {
            return getState();
        }
        /**
         * 这个AQS的方法被重写了,当前实现逻辑是:只有当计数器 == 0 时,才获取到锁
         */
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
 
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
 
    private final Sync sync;
 
    /**
     * 计数锁存器 构造函数,当 count < 0 抛出非法参数异常
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
 
    /**
     * 调用该方法后,当前线程进入等待,直到计数器减为0,或者线程被打断
     */
    public void await() throws InterruptedException {
        // 获取共享锁,方法中调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,
        // 否则就调用doAcquireSharedInterruptibly()使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。
        sync.acquireSharedInterruptibly(1);
    }
 
    /**
     * 调用该方法后,当前线程进入有限时间等待,直到计数器减为0,或者线程被打断,或者等待时间到
     * 1.超过指定的等待时间,计数还没到0,返回false
     * 2.当设置的超时时间 <= 0,调这个方法也不会进入等待
     */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
 
    /**
     * 子线程调用,递减计数
     */
    public void countDown() {
        sync.releaseShared(1);
    }
 
    /**
     * 获取当前计数值
     */
    public long getCount() {
        return sync.getCount();
    }
 
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

附:AQS中的 doAcquireSharedInterruptibly 方法(没有获取到共享锁的线程会调该方法进入等待)解析:

private void doAcquireSharedInterruptibly(long arg)
    throws InterruptedException {
    // 创建"当前线程"的Node节点,且Node中记录的锁是"共享锁"类型;并将该节点添加到CLH队列末尾。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取上一个节点。
            // 如果上一节点是CLH队列的表头,则"尝试获取共享锁"。
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // (上一节点不是CLH队列的表头) 当前线程一直等待,直到获取到共享锁。
            // 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            // 抛出异常 会到这里
            cancelAcquire(node);
    }
}

二、CyclicBarrier(可循环利用屏障)
生活场景:吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始
1.CyclicBarrier 与 CountDownLatch 区别:
    1) CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
    2) CountDownLatch 的每个子线程职责可以不一样;CyclicBarrier 每个线程在某一次全部到达屏障前,完成的动作一致
    3) CountDownLatch 是允许1个或N个线程等待其他线程完成执行; CyclicBarrier 则是N个线程的相互等待
    4) 一般来说用CyclicBarrier可以实现CountDownLatch的功能,而反之则不能

2.可循环利用屏障代码示例

public class CyclicBarrierDemo {

    public static void main(String[] args) {

        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            // tip:这里可以获取到最后执行完任务的线程
            System.out.println(Thread.currentThread().getName() + "最后到达屏障");
        });

        for (int i = 0; i < 5; i++) {
            // 所有的线程使用同一个屏障,满5可再重复利用
            MyThread myThread = new MyThread(cyclicBarrier);
            myThread.start();
        }
    }
}

class MyThread extends Thread {

    private CyclicBarrier cyclicBarrier;

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

    @Override
    public void run() {
        try {
            System.out.println(getName() + "线程正在执行任务A");
            cyclicBarrier.await();
            // 上面是每个线程到达屏障前都会做 任务A
            System.out.println(getName() + "线程完成执行任务A");
            System.out.println(getName() + "线程正在执行任务B");
            cyclicBarrier.await();
            // 上面是每个线程到达屏障前都会做 任务B
            System.out.println(getName() + "线程完成执行任务B");
        } catch (Exception e) {

        }
    }
}

执行结果:

Thread-0线程正在执行任务A
Thread-4线程正在执行任务A
Thread-1线程正在执行任务A
Thread-2线程正在执行任务A
Thread-3线程正在执行任务A
Thread-0最后到达屏障
Thread-0线程完成执行任务A
Thread-1线程完成执行任务A
Thread-1线程正在执行任务B
Thread-4线程完成执行任务A
Thread-4线程正在执行任务B
Thread-3线程完成执行任务A
Thread-3线程正在执行任务B
Thread-0线程正在执行任务B
Thread-2线程完成执行任务A
Thread-2线程正在执行任务B
Thread-2最后到达屏障
Thread-2线程完成执行任务B
Thread-4线程完成执行任务B
Thread-1线程完成执行任务B
Thread-0线程完成执行任务B
Thread-3线程完成执行任务B

3.源码分析
    1) 它是 ReentrantLock 和 Condition 的组合使用
    2) CyclicBarrier 的成员变量

//同步操作锁
private final ReentrantLock lock = new ReentrantLock();
//通过这个条件队列对线程进行阻塞的
private final Condition trip = lock.newCondition();
//每次循环拦截的线程数
private final int parties;
//换代前执行的任务
private final Runnable barrierCommand;
//表示屏障的当前代
private Generation generation = new Generation();
//计数器 初始值 = parties,有线程await时,count--
private int count;
 
//静态内部类Generation
private static class Generation {
  boolean broken = false;
}

    3) 原理:CyclicBarrier 内部有个计数器,每个线程到达屏障点时,会调用 await 将自己阻塞,此时计数器减1,当计数器减到0的时候,所有调await将自己阻塞的线程被唤醒

    4) 线程 执行 barrier.await()方法,内部调用下面的doAwait()方法

//核心等待方法
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
  // 使用了 ReentrantLock
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    final Generation g = generation;
    //检查当前屏障是否被打破
    if (g.broken) {
      throw new BrokenBarrierException();
    }
    //检查当前线程是否被中断
    if (Thread.interrupted()) {
      //如果当前线程被中断会做以下三件事
      //1.打破当前屏障
      //2.唤醒拦截的所有线程
      //3.抛出中断异常
      breakBarrier();
      throw new InterruptedException();
    }
    //每次都将计数器的值减1
    int index = --count;
    //计数器的值减为0则需唤醒所有线程并转换到下一代
    if (index == 0) {
      boolean ranAction = false;
      try {
        //唤醒所有线程前先执行指定的任务
        final Runnable command = barrierCommand;
        if (command != null) {
          // index=0的最后一个线程执行 预设 的都到达屏障点 执行的方法
          command.run();
        }
        ranAction = true;
        //唤醒所有线程并转到下一代
        nextGeneration();
        return 0;
      } finally {
        //确保在任务未成功执行时能将所有线程唤醒
        if (!ranAction) {
          breakBarrier();
        }
      }
    }
 
    //如果计数器不为0则执行此循环
    for (;;) {
      try {
        //根据传入的参数来决定是定时等待还是非定时等待
        if (!timed) {
          trip.await();
        }else if (nanos > 0L) {
          nanos = trip.awaitNanos(nanos);
        }
      } catch (InterruptedException ie) {
        //若当前线程在等待期间被中断则打破屏障唤醒其他线程
        if (g == generation && ! g.broken) {
          breakBarrier();
          throw ie;
        } else {
          //若在捕获中断异常前已经完成在屏障处的等待, 则直接调用中断操作
          Thread.currentThread().interrupt();
        }
      }
      //如果线程因为打破屏障操作而被唤醒则抛出异常
      if (g.broken) {
        throw new BrokenBarrierException();
      }
      //如果线程因为换代操作而被唤醒则返回计数器的值
      if (g != generation) {
        return index;
      }
      //如果线程因为时间到了而被唤醒则打破屏障并抛出异常
      if (timed && nanos <= 0L) {
        breakBarrier();
        throw new TimeoutException();
      }
    }
  } finally {
    lock.unlock();
  }
}

//切换屏障到下一代方法(当屏障被打破后的动作执行完成,开启下一代)
private void nextGeneration() {
  //唤醒条件队列所有线程
  trip.signalAll();
  //设置计数器的值为需要拦截的线程数
  count = parties;
  //重新设置屏障代
  generation = new Generation();
}
 
//打破当前屏障方法
private void breakBarrier() {
  //将当前屏障状态设置为打破
  generation.broken = true;
  //设置计数器的值为需要拦截的线程数
  count = parties;
  //唤醒所有线程
  trip.signalAll();
}

// 重置屏障方法
public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

三、Semaphore
1.使用场景:用于管理一组资源,可用于多个线程抢多个共享资源。既用于多个共享资源的互斥使用,又控制并发线程数
2.特殊情况下,Semaphore可以代替 Synchronized或reentrantLock,即对线程抢单一资源
3.使用示例:

public class SemaphoreDemo {

    public static void main(String[] args) {

        // 模拟6个车位
        Semaphore sem = new Semaphore(6);

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    // 没抢到的线程会被阻塞
                    sem.acquire();
                    System.out.println("线程" + Thread.currentThread().getName() + "\t 抢到车位");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程" + Thread.currentThread().getName() + "\t 离开车位");
                } catch (InterruptedException e) {
                    System.out.println("打断异常");
                } finally {
                    sem.release();
                }
            }, String.valueOf(i)).start();
        }

    }

}

执行结果:

线程0     抢到车位
线程1     抢到车位
线程2     抢到车位
线程3     抢到车位
线程4     抢到车位
线程5     抢到车位
线程3     离开车位
线程1     离开车位
线程0     离开车位
线程4     离开车位
线程2     离开车位
线程5     离开车位
线程9     抢到车位
线程8     抢到车位
线程7     抢到车位
线程6     抢到车位


参考:深入理解CyclicBarrier原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值