赞
踩
目录
3.1:构造器Lock lock = new ReentrantLock()
3.4:AbstractQueuedSynchronizer源码分析和数据结构
4.2:ReentrantReadWriteLock 分段锁,读写分离
juc是java.util.concurrent包下的工具类,提供了跟synchronize相似的并发控制。在此包中增加了在并发编程中很常用的工具类。有了synchronize关键字,为什么还要学习JUC呢
对比 | juc | synchronize |
原理构成: | 工具类,通过API层面的锁来实现 | JVM关键字,底层编译后是monitorenter和monitorenter 关键字,锁定对象 |
使用方法: | 代码加锁,用户需要手动的加锁和释放锁,需要lock和unlock | 方法或者代码块加锁,不需要释放 |
是否可以中断: | | 不可以中断,要么执行,要么等待 |
是否公平: | 可以公平也可以不公平 | 不公平 |
知否重入: | 重入锁 | 重入锁 |
是否能精准唤醒 | 可以,绑定condition | 不可以,解锁后线程随机竞争 |
其他功能拓展 | 0:condition对象,指定唤醒 1:AtomicInteger元子类,线程安全 2:ReentrantReadWriteLock 分段锁,读写分离 3:CountDownLatch(减法计数器) 4:Semaphore(信号量) 5:CyclicBarrier(加法计数器) 6:ThreadPoolExecutor(线程池) 等等多种扩展功能 | 没有扩展功能 |
1:构造ReentrantLock重入锁,默认非公平锁
2:使用lock加锁和unlock解锁,使用finally解锁
对比synchronize方法,代码控制
- /**
- * @author :huyiju
- * @date :8/5/21 5:05 PM
- * <p>
- * ReentrantLock重入锁
- * lock加锁 unlock解锁 使用try catch finally
- */
- public class LockT1 {
- public static void main(String[] args) {
- Ticks ticks = new Ticks();
- //线程1抢票
- new Thread(() -> {
- for (int i = 0; i < 20; i++) {
- ticks.sale();
-
- }
- }, "a").start();
- //线程2抢票
- new Thread(() -> {
- for (int i = 0; i < 20; i++) {
- ticks.sale();
-
- }
- }, "b").start();
-
- }
- }
-
- class Ticks {
- //票数20张
- int num = 20;
- //构造ReentrantLock重入锁
- Lock lock = new ReentrantLock();
-
- public void sale() {
- //加锁
- lock.lock();
- try {
- if (num > 0) {
- System.out.println("线程" + Thread.currentThread().getName() + "买了第" + (num--) + "张票");
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- //解锁
- lock.unlock();
- }
- }
- }
以上就是juc加锁解锁实现线程安全的用法。我们要想知道具体的实现,为什么就能实现线程安全只需要查看源码就能知道了。
在源码分析直前,我们首先要知道这个类图结构,后续的代码分析,也会加深这个结构理解
1:Lock lock = new ReentrantLock();
通过构造器创建公平锁和非公平锁,我们暂时先不管这是什么意思,接着看第二步
- //第一步:无参构造创建非公平锁
- public ReentrantLock() {
- sync = new NonfairSync();
- }
-
- //第一步:有参构造创建锁 true公平锁 false非公平锁
- public ReentrantLock(boolean fair) {
- sync = fair ? new FairSync() : new NonfairSync();
- }
lock.lock()加锁,这一步较为复杂,我们分解为多个步骤解析分为公平锁和非公平锁
1:非公平锁代码,加锁的过程中不管有多少个线程堵塞都会优先抢占资源
这里的核心难点是
- static final class NonfairSync extends Sync {
- //非公平锁加锁,首先就回进行CAS操作,操作AQS中State从0变1,然后执行该线程
- final void lock() {
- //if条件是cas 操作aqs中的state值,默认是0,0的话没有加锁,当前线程加锁后
- //将state的值变成1,其他的线程进来看到state已经是1的话,就等待。实现了
- //并发的线程安全
- if (compareAndSetState(0, 1))
- setExclusiveOwnerThread(Thread.currentThread());
- else
- acquire(1);
- }
-
- }
2:公平锁代码,加锁的过程中直接去排队
- static final class FairSync extends Sync {
- //公平锁,直接排队
- final void lock() {
- acquire(1);
- }
- }
3:公平锁和非公平锁acquire方法逻辑
- //*公平锁和非公平锁的acquire方法
- public final void acquire(int arg) {
- //此处有三个方法我们逐一解析
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
-
- //方法1:tryAcquire方法此处公平锁和非公平锁存在差别
- protected final boolean tryAcquire(int acquires) {
- //获取当前线程
- final Thread current = Thread.currentThread();
- //获取aqs中的state
- int c = getState();
- //=0 表示没有线程,自己是第一个
- if (c == 0) {
- //此处代码
- // 非公平锁没有!hasQueuedPredecessors()这行代码,判断队列是否有数据,直接cas抢夺资源
- // 公平锁在此处直接看看队列有没有数据进行排队 若果不需要排队能进行CAS 则运行代码
- if (!hasQueuedPredecessors() &&
- compareAndSetState(0, acquires)) {
- setExclusiveOwnerThread(current);
- return true;
- }
- } else if (current == getExclusiveOwnerThread()) {
- //如果是当前线程是运行线程
- //将state的值+1,这里体现了可重入锁,对应上边代码的两个lock state=2
- int nextc = c + acquires;
- if (nextc < 0)
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
- return false;
- }
-
- //方法2:addWaiter,将当前线程封装成node,
- // 放入由node组成的双向链表的最后,并且返回节点
- private Node addWaiter(Node mode) {
- Node node = new Node(Thread.currentThread(), mode);
- // Try the fast path of enq; backup to full enq on failure
- Node pred = tail;
- if (pred != null) {
- node.prev = pred;
- if (compareAndSetTail(pred, node)) {
- pred.next = node;
- return node;
- }
- }
- enq(node);
- return node;
- }
-
- //方法3:acquireQueue获取队列,将当前线程封装成node,
- final boolean acquireQueued(final Node node, int arg) {
- boolean failed = true;
- try {
- boolean interrupted = false;
- for (;;) {//死循环
- final Node p = node.predecessor();//获得该node的前一个节点
- //如果当前节点是第一个节点,代表排队第一个人
- //尝试tryAcquire获取锁,tryAcquire非公平锁和公平锁的代码不相同,代码复用
-
- if (p == head && tryAcquire(arg)) {
- setHead(node);
- p.next = null; // help GC,将前置节点的下个节点设置为空,这样就没有指针指向它,可以被gc回收
- failed = false;
- return interrupted;//返回false表示不能被打断,意思是没有被挂起,也就是获得到了锁
- }
-
- //不是第一个排队的
- //shouldParkAfterFailedAcquire这里表示将节点挂起,
- //并且将当前节点状态设置SIGNAL,不能放弃状态值CANCELLED=1的节点
- if (shouldParkAfterFailedAcquire(p, node) &&
- parkAndCheckInterrupt())//挂起线程 park() 等待unlock唤醒线程
- interrupted = true;
- }
- } finally {
- if (failed)
- cancelAcquire(node);
- //如果失败取消尝试获取锁(从上面的代码看只有进入p == head && tryAcquire(arg)这个逻辑是才会触发,
- // 这个时候前置节点正好在当前节点入队的时候执行完,当前节点正好获得锁,具体的代码以后分析)
- }
- }
-
-
- private final boolean parkAndCheckInterrupt() {
- LockSupport.park(this);
- return Thread.interrupted();//这里返回是否被打断,在lockInterruptibly中有意义,在lock中没有意义,个人理解只是为了代码复用。
- }
4:总结
1:通过构造器构建公平和非公平锁
2:在lock方法中 公平锁进来直接acquire,非公平锁CAS尝试回去资源然后acquire
3:acquire方法包含以下三个方法
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
4:tryAcquire公平锁和非公平再次获取锁,有队就排是公平锁,不排队先尝试是非公平锁,都获取不到锁的时候执行addWaiter
5:addWaiter方法将节点添加到aqs中组成双向队列,通过aqs保证安全性。
6:acquireQueued再起尝试看看前边的线程执行完了没有(再起调用tryAcquire),如果自己是第一个排队的,执行自己的线程。没有的话LockSupport.park(this)暂停线程。
- //第一步unlock方法:释放锁,aqs中的state的状态值-1
- public void unlock() {
- sync.release(1);
- }
-
- //第二步release方法:首先执行执行tryRelease方法
- public final boolean release(int arg) {
- if (tryRelease(arg)) {//解锁成功
- AbstractQueuedSynchronizer.Node h = head;//获取头部节点 不为空和头部状态不是0
- if (h != null && h.waitStatus != 0)
- unparkSuccessor(h);//参数是头部节点
- return true;
- }
- return false;
- }
-
-
-
- //第三步tryRelease方法:解锁操作,将state-1 如说是0的话 返回true执行第四部唤醒方法
- protected final boolean tryRelease(int releases) {
- int c = getState() - releases;
- //不是的那个前线程释放锁,报异常
- if (Thread.currentThread() != getExclusiveOwnerThread())
- throw new IllegalMonitorStateException();
- boolean free = false;
- if (c == 0) {
- free = true;
- setExclusiveOwnerThread(null);//=0 解锁成功 线程设置为null
- }
- setState(c);
- return free;
- }
-
- //第四步unparkSuccessor唤醒方法:从头到尾的唤醒
- private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
- int ws = node.waitStatus;
- if (ws < 0)
- compareAndSetWaitStatus(node, ws, 0);
- AbstractQueuedSynchronizer.Node s = node.next;
- if (s == null || s.waitStatus > 0) {
- s = null;
- for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
- if (t.waitStatus <= 0)
- s = t;
- }
- if (s != null)//唤醒头部节点的下一个
- LockSupport.unpark(s.thread);
- }
总结:
unlock方法:对state-1的时候没有cas,而是判断线程是否是运行的线程,很聪明的做法,
1:尝试释放锁,将state的值重置为0,这里的原始值可能大于1是因为重入的原因
2:成功的话,解锁。unpark接下来的节点。从头到尾的唤醒。
1:源码分析,我们分析有效的源码可以知道下图的数据结构
- public class AbstractQueuedSynchronizer {
-
- private transient volatile AbstractQueuedSynchronizer.Node head;//链表的头部节点
- private transient volatile AbstractQueuedSynchronizer.Node tail;//链表的末尾节点
- private volatile int state;//状态值默认是0未使用,>1重入锁
-
- //aqs的内置静态类 也就是双向链表的结构
- static final class Node {
- static final int CANCELLED = 1;//线程取消
- static final int SIGNAL = -1;//线程等待中
- static final int CONDITION = -2;//线程在等待条件
- static final int PROPAGATE = -3;//暂时没有到
- volatile int waitStatus;//节点状态 默认是0
-
- volatile AbstractQueuedSynchronizer.Node prev;//前节点
- volatile AbstractQueuedSynchronizer.Node next;//后节点
-
- volatile Thread thread;//节点的线程
-
- }
-
- //此处省略各种修改state的值得方法和链表修改的方法
- }
2:抽象对列同步器的数据结构FIFO,头部插入尾部唤醒
1:condition的类图
如下图我们知道conditionObject是AQS的内部类,实现condition接口,我们查看图如下
2:condition和object的区别
区别 | condition需要和ReentrantLock配合使用 | object的方法,需要和synchronize配合使用 |
线程阻塞 | await | wait |
线程唤醒 | signal(唤醒队列的第一个) signalAll(唤醒队列的全部,排队运行) | notify(随机一个) notifyAll (唤醒全部,争抢锁) |
3:案例代码使用
- /**
- * @author :huyiju
- * @date :8/5/21 5:05 PM
- *
- * 线程通信使用lock 和Condition接口
- *
- * Condition接口 实现循环唤醒
- */
- public class LockT4 {
- public static void main(String[] args) {
- TT2 tt = new TT2();
-
- new Thread(() -> {
- for (int i = 0; i < 3; i++) {
- tt.a1();
- }
- }, "aaa").start();
-
- new Thread(() -> {
- for (int i = 0; i < 3; i++) {
- tt.a2();
- }
- }, "bbb").start();
-
- new Thread(() -> {
- for (int i = 0; i < 3; i++) {
- tt.a3();
- }
- }, "ccc").start();
- }
- }
-
- class TT2 {
- int num = 1;
- Lock lock=new ReentrantLock();
-
- //此处设置三个condition,维护了3个condition队列
- //a1方法执行完唤醒a2
- //a2方法执行完唤醒a3
-
- Condition condition1=lock.newCondition();//需要和lock配合使用
- Condition condition2=lock.newCondition();
- Condition condition3=lock.newCondition();
-
- //开始num=1 执行a1 将num=2 唤醒a2
- public void a1() {
- lock.lock();
- try {
- while (num!=1){
- condition1.await();
- }
- num=2;
- System.out.println("当前线程:"+Thread.currentThread().getName());
- condition2.signal();//当前线程a,指定唤醒线程b
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
-
-
- }
-
- //num=2 执行a2 将num=3 唤醒a3
- public void a2() {
- lock.lock();
- try {
- while (num!=2){
- condition2.await();
- }
- num=3;
- System.out.println("当前线程:"+Thread.currentThread().getName());
- condition3.signal();//当前线程b,指定唤醒线程c
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
-
-
- }
-
- //num=3 执行a1 将num=1 唤醒a1
- public void a3() {
- lock.lock();
- try {
- while (num!=3){
- condition3.await();
- }
- num=1;
- System.out.println("当前线程:"+Thread.currentThread().getName());
- condition1.signal();//当前线程c,指定唤醒线程a
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
-
- }
-
-
- }
4:源码分析
数据结构:单向队列
- //单向队列,firstNode-> node1 -> node2-> lastNode
- public class ConditionObject implements Condition, java.io.Serializable {
- /** 第一个节点 condition queue. */
- private transient AbstractQueuedSynchronizer.Node firstWaiter;
- /** 最后一个节点 condition queue. */
- private transient AbstractQueuedSynchronizer.Node lastWaiter;
-
- }
第一步:Condition condition1=lock.newCondition();通过lock接口创建了一个conditionObject,也就是aqs内部类。
第二步:condition1.await(),方法将node添加到单向队列,当前线程阻塞
- public final void await() throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- //将节点添加到单向队列,返回该节点,节点元素之后线程ID和Node.CONDITION=-2
- //AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), AbstractQueuedSynchronizer.Node.CONDITION);
- AbstractQueuedSynchronizer.Node node = addConditionWaiter();
-
- //释放资源 调用park方法
- int savedState = fullyRelease(node);
- int interruptMode = 0;
- // 如果当前节点不在【同步队列】中, 线程进入阻塞状态,等待被唤醒
- while (!isOnSyncQueue(node)) {
- LockSupport.park(this);
- if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
- break;
- }
- if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
- interruptMode = REINTERRUPT;
- if (node.nextWaiter != null) // clean up if cancelled
- unlinkCancelledWaiters();
- if (interruptMode != 0)
- reportInterruptAfterWait(interruptMode);
- }
第三步:condition2.signal()方法,唤醒队列的下一个或者全部
- public final void signal() {
- if (!isHeldExclusively())
- throw new IllegalMonitorStateException();
- AbstractQueuedSynchronizer.Node first = firstWaiter;//得到头部节点
- if (first != null)
- doSignal(first);//唤醒头部节点的下一个
- doSignalAll(first);//唤醒头部节点的之后的全部
- }
1:ReentrantReadWriteLock作用
读的时候加读锁不让写,多个读线程可以同时读数据。
写的时候加写锁不让读,只能单个线程写。
能保证写的线程安全,又能保证读的效率高
2:案例使用
- public class ReadWriteLockT1 {
- public static void main(String[] args) {
- Book book = new Book();
- //此处for循环6个线程写数据,写入方法加锁
- for (int i = 1; i < 4; i++) {
- final int num = i;
- new Thread(() -> {
- book.write(num + "", num + "");
- }, String.valueOf(i)).start();
-
- }
-
-
-
-
- //for循环10个线程读数据,不需要加锁
- for (int i = 0; i < 5; i++) {
- final int num = i;
- new Thread(() -> {
- book.read(num + "");
- }, String.valueOf(i)).start();
-
- }
- }
-
-
- }
-
- class Book {
- Map<String, String> map = new HashMap();
- //第一步:使用ReentrantReadWriteLock
- ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
-
- //读 必须加锁 防止读的时候写线程数据写入
- public void read(String key) {
- readWriteLock.readLock().lock();
- try {
- System.out.println("当前线程" + Thread.currentThread().getName() + "读-");
- map.get(key);
- System.out.println("当前线程" + Thread.currentThread().getName() + "读-完毕");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- //读的时候解锁
- readWriteLock.readLock().unlock();
- }
- }
-
- //写 只能等到单个线程写完 才能下个线程接着写
- public void write(String key, String value) {
- readWriteLock.writeLock().lock();
- try {
- System.out.println("当前线程" + Thread.currentThread().getName() + "写");
- map.put(key, value);
- System.out.println("当前线程" + Thread.currentThread().getName() + "写完毕");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- readWriteLock.writeLock().unlock();
- }
-
- }
- }
1:AtomicInteger原子类作用
int a; a++ 难以保证多线程的数据安全。所有我们使用原子类,他的incrementAndGet方法是CAS操作,保证了线程安全。使用do while循环操作。其实还有很多方法都是CAS啦。但是CAS自旋z在多并发下会消耗CPU。
2:代码使用
- /**
- * @author :huyiju
- * @date :8/11/21 5:54 PM
- * 原子类的各种CAS操作验证
- */
- public class Cas1 {
-
- public static void main(String[] args) {
-
- //构造方法创建默认值 静态方法获取构造器传递的的内存偏移值
- AtomicInteger atomicInteger = new AtomicInteger(10);
- //compareAndSet 调用c++方法 传递(偏移量 旧值 新值)
- System.out.println("CAS赋值11是否成功:"+atomicInteger.compareAndSet(10, 11));
- System.out.println("CAS赋值11后的值:"+atomicInteger);
-
- //已经是11了 无法操作
- System.out.println("CAS赋值11是后,再次从10 -11赋值:"+atomicInteger.compareAndSet(10, 11));
-
- //+1操作
- System.out.println("CAS赋值11后再次自增:"+atomicInteger.incrementAndGet());
- }
- }
3:ABA问题代码使用AtomicStampedReference解决
-
- System.out.println("--------------原子引用ABA问题 初始化版本号是1-----------------");
- AtomicStampedReference atomicStampedReference = new AtomicStampedReference(128, 1);
-
-
- new Thread(() -> {
- System.out.println(atomicStampedReference.compareAndSet(128, 2, atomicStampedReference.getStamp(),
- atomicStampedReference.getStamp() + 1));
- System.out.println("捣乱线程2,值:" + atomicStampedReference.getReference());
-
- System.out.println(atomicStampedReference.compareAndSet(2, 128,
- atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
- System.out.println("捣乱线程2,值:" + atomicStampedReference.getReference());
-
- System.out.println("-------------");
- System.out.println("版本号是:"+atomicStampedReference.getStamp());
- System.out.println("捣乱线程2:" + atomicStampedReference.getReference());
-
- }).start();
-
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- System.out.println("--------------");
- System.out.println(atomicStampedReference.compareAndSet(1, 2, 1,
- 2));
-
- System.out.println("正经线程:" + atomicStampedReference.getReference());
-
- }
1:CountDownLatch作用
主要是定义几个线程,执行一些操作,等这些操作完成之后在执行主线程
2:案例使用(主要是维护了aqs的state值,通过每次减一等于0的时候执行主线程)
- /**
- * 减法计数器
- *
- * CountDownLatch:
- *
- * 允许一个或多个线程等待其他一组线程完成操作,
- * 它使用一个计数器来初始化需要等待的线程数量,每当一个线程完成了它的任务,计数器就会递减,当计数器归零时,
- * 意味着所有需要等待的线程都已经完成了它们的任务,此时等待的线程(通常是一个或多个主线程)就可以继续执行了。
- *
- * 作用:
- * 确保一组线程都完成后才触发其他线程的执行,适用于资源加载、任务初始化等场景
- */
- public class CountDownLatchT1 {
- public static void main(String[] args) {
- //此处的计数器 计数器需要小于或者等于线程数 要不然countDownLatch.await() 永远不为0
-
- //第一步:将AQS中的state的值设为5
- CountDownLatch countDownLatch=new CountDownLatch(5);
- for (int i = 0; i < 4; i++) {
- new Thread(() -> {
- System.out.println("当前线程:"+Thread.currentThread().getName());
- System.out.println("当前线程做了很多实行,加载配置文件,读数据库等操作");
- countDownLatch.countDown();//每次state减1
- }, String.valueOf(i)).start();
-
- }
-
- try {
- countDownLatch.await();//等到计数器是0 才能向下执行
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("countdownlunch方法执行完毕,执行主线程");
- }
- }
1:Semaphore作用
主要是限流,比如网站的流控,就像车道一样。多个线程限制执行小部分线程。也是操作state,将线程加入node的队列。park和unpark操作。
2:案例使用
- /**
- * 信号量
- *
- */
- public class SemaphoreTest1 {
- public static void main(String[] args) {
- Semaphore semaphore = new Semaphore(3);
-
- //创建5个线程,一共3个信号量,相当于3个车位
- //当开始的3个线程抢到信号量,可以停车,等着3个线程离开,剩下的2个线程,在抢车位
- for (int i = 0; i < 5; i++) {
- int finalI = i;
- new Thread(() -> {
- try {
- //第二步:获取state信号量 如果<0 线程加入队列park 否则线程执行
- semaphore.acquire();//相当于减1,车位减1
- System.out.println(Thread.currentThread().getName() + "抢到车位:"+ finalI);
- TimeUnit.SECONDS.sleep(3);
- System.out.println(Thread.currentThread().getName() + "离开车位:"+finalI);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }finally {
- semaphore.release();//用完之后+1,车位加回来
- }
-
- },"线程"+i).start();
- }
-
- System.out.println("Main主线程结束");
- }
- }
结果:
1:CyclicBarrier作用,设置一个计数器比如是3,假如10个线程来了,三个一组进入执行。
计数器必须和线程数一致
栅栏的开口>线程 永远等待 人少了永远在门口等待
栅栏的开口<线程 永远等待 人多了,多的人在门口永远等待,其他的人进去
2:案例使用
- /**
- * @author :huyiju
- * @date :8/9/21 6:23 PM
- * 栅栏 线程到达栅栏进行等待,等人齐了 在开始下一步执行
- */
- public static void main(String[] args) {
-
- //计数器必须和线程数一致
- //计数器>线程 永远等待 人少了永远在门口等待
- //计数器<线程 有的执行 人多了多的人在门口等待
- CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2, new Runnable() {
- @Override
- public void run() {
-
- System.out.println("栅栏每放行一批的,执行一次");
- }
- });
- for (int i = 0; i < 6; i++) {
-
- new Thread(() -> {
- try {
- //线程必须进入栅栏,等待全部的线程
- System.out.println("当前线程:" + Thread.currentThread().getName() + "进入栅栏");
- //线程进来将计数器-1,指导等于0,在执行,也就是进来4个线程才能执行
- cyclicBarrier1.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (BrokenBarrierException e) {
- throw new RuntimeException(e);
- }finally {
- //cyclicBarrier1.reset();
- }
- System.out.println(Thread.currentThread().getName() + "--执行完毕");
- }).start();
- }
-
- }
案例分析:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。