Lock和ReentrantLock
JDK 1.5中提供了锁的接口:java.util.concrrent.locks.lock,提供了ReentrantLock,ReentrantReadWriteLock实现类。
Lock接口
Lock接口提供了比Synchronized方法更加灵活的锁的操作接口,可以具有很大的属性,支持多个相关Condition对象(线程间通信,Object方法提供的wait、notify、notifyAll)
Lock接口提供的方法操作:
Lock
void lock()
获取锁,如果锁被使用会一直阻塞直至获取到锁。
lockInterruptibly()
void lockInterruptibly() throws InterruptedException
如果当前线程未被中断,则获取锁
如果锁可用,则获取锁,并立即返回,如果在加锁过程中发生了Interrupt中断操作,会抛出InterruptedException异常,并中断掉当前线程的加锁状态。
tryLock()
boolean tryLock()
尝试获取锁,仅在调用是锁为空闲状态才获取锁,如果锁是可用的返回true,如果锁不可用,该方法会立即返回false。
tryLock()
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
在指定时间内尝试性获取锁。
unlock
void unlock()
释放锁,加锁和释放锁是成对出现的,对应于lock()、tryLock()、tryLock(xx)、lockInterruptibly()等操作,如果成功的话应该对应一个unlock操作,这样可以避免死锁或者资源的浪费。
newCondition()
Condition newCondition()
返回绑定到Lock实例的新的Condition实例,可以进行线程间通信。
AQS
AQS原理
AQS:AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制。
AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包。
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。
用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
AQS实现的具体方式如下:
如图示,AQS维护了一个volatile int state和一个FIFO线程等待队列,多线程争用资源被阻塞的时候就会进入这个队列。state就是共享资源,其访问方式有如下三种:
getState();setState();compareAndSetState();
AQS 定义了两种资源共享方式:
1.Exclusive:独占,只有一个线程能执行,如ReentrantLock
2.Share:共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier
不同的自定义的同步器争用共享资源的方式也不同。
注意:AQS是自旋锁:在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物。
AQS(AbstractQueuedSynchronizer)是J.U.C下较复杂的一个类,提供了一个为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。
通过上面查看类的子类的层级关系可知:AQS是CountdownLatch、ReentrankLock、ThreadPoolExecutor、ReentrankReadWriterLock和Semaphore是实现的基础。
AQS核心字段:
主要有三个核心字段:
private transient volatile Node head;
private transient volatile Node
tail; private volatile int state;
其中state是描述有多少个线程获取锁。
state=0:表示锁第空闲状态
state>0:表示锁被占用
state<0:表示溢出
head和tail加上CAS就构成了一个FIFO队列,是一个双向链表,每个节点是Node类型。
static final class Node {
/** 标记表示节点正在共享模式中等待 */
static final Node SHARED = new Node();
/** 标记表示节点正在独占模式下等待 */
static final Node EXCLUSIVE = null;
/**
* 表示线程已经被取消
* 同步队列中的线程因为超时或中断,需要从同步队列中取消。被取消的节点将不会有任何改变
*/
static final int CANCELLED = 1;
/**
* 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后
* 继节点,使后继节点的线程得以运行
*/
static final int SIGNAL = -1;
/**
* 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法
* 后,该节点将会中等待队列中转移到同步队列中,加入到对同步状态的获取
*/
static final int CONDITION = -2;
/**
* 下一次共享模式同步状态获取将会无条件的被传播下去
*/
static final int PROPAGATE = -3;
/**
* 等待状态,仅接受如下状态中的一个值:
* SIGNAL: -1
* CANCELLED: 1
* CONDITION: -2
* PROPAGATE: -3
* 0: 初始化的值
*
* 对于正常的同步节点,它的初始化值为0,对于条件节点它的初始化的值是CONDITION。它使用
* CAS进行修改。
*/
volatile int waitStatus;
/**
* 前驱节点
*/
volatile Node prev;
/**
* 后继节点
*/
volatile Node next;
/**
* 获取同步状态的线程
*/
volatile Thread thread;
/**
* 等待队列中的后继节点。如果当前节点是共享的,那么这个字段是一个SHARED常量,也就是说
* 节点类型(独占和共享)和等待队列中的后继节点公用同一个字段
*/
Node nextWaiter;
/**
* 如果节点在共享模式下等待则返回true
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 获取前驱节点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
volatile int waitStatus:节点的等待状态,一个节点可能位于以下几种状态:
CANCELLED = 1
当前的线程被取消,节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。
SIGNAL = -1
表示当前节点的后继节点包含的线程需要运行,也就是unpark.节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。
CONDITION = -2
当前节点在等待condition,也就是在condition队列中.表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。PROPAGATE=-3当前场景下后续的acquireShared能够得以执行。
0
当前节点在sync队列中,等待着获取锁
正常状态,新生的非CONDITION节点都是此状态。
非负值标识节点不需要被通知(唤醒)。
volatile Node prev;此节点的前一个节点。节点的waitStatus依赖于前一个节点的状态。
volatile Node next;此节点的后一个节点。后一个节点是否被唤醒(uppark())依赖于当前节点是否被释放。
volatile Thread thread;节点绑定的线程。
Node nextWaiter;下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。
节点(Node)是构成CHL的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程会构建成一个节点并加入到同步器的尾部。CHL的基本结构如下:
AQS同步器原理
基本的思想是表现为一个同步器,支持下面两个操作:
获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞线程,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,并且如果进了队列就从队列中移除。
while(synchronization state does not allow acquire){
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;
释放锁:这个过程就是修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。
update synchronization state;
if(state may permit a blocked thread to acquire)
unlock one or more queued threads;
要支持上面两个操作就必须有下面的条件:
● 原子性操作同步器的状态位
● 阻塞和唤醒线程
● 一个有序的队列
目标明确,要解决的问题也清晰了,那么剩下的就是解决上面三个问题。
● 状态位的原子操作
这里使用一个32位的整数来描述状态位,使用CAS操作来修改状态。事实上这里还有一个64位版本的同步器(AbstractQueuedLongSynchronizer),这里暂且不谈。
● 阻塞和唤醒线程
标准的JAVA API里面是无法挂起(阻塞)一个线程,然后在将来某个时刻再唤醒它的。JDK 1.0的API里面有Thread.suspend和Thread.resume,并且一直延续了下来。但是这些都是过时的API,而且也是不推荐的做法。
在JDK 1.5以后利用JNI在LockSupport类中实现了此特性。
LockSupport.park()
LockSupport.park(Object)
LockSupport.parkNanos(Object, long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(Object, long)
LockSupport.parkUntil(long)
LockSupport.unpark(Thread)
上面的API中park()是在当前线程中调用,导致线程阻塞,带参数的Object是挂起的对象,这样监视的时候就能够知道此线程是因为什么资源而阻塞的。由于park()立即返回,所以通常情况下需要在循环中去检测竞争资源来决定是否进行下一次阻塞。park()返回的原因有三:
● 其他某个线程调用将当前线程作为目标调用 unpark;
● 其他某个线程中断当前线程;
● 该调用不合逻辑地(即毫无理由地)返回。
其实第三条就决定了需要循环检测了,类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。
● 有序队列
在AQS中采用CHL列表来解决有序的队列的问题。
AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。
查看类的方法和属性
快捷键:comand(win)+7
非快捷键:类的顶部栏:View->Tool Windows->Structure
对于入队列(enqueue):从数据结构上出发,入列是比较简单的,无非就是当前队列中的尾节点指向新节点,新节点的prev指向队列中的尾节点,然后将同步器的tail节点指向新节点。在AQS中入列的源码如下:
/**
* 为当前线程和给定的模式创建节点并计入到同步队列中
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
// 创建一个节点
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试添加尾节点,如果失败则调用enq(Node node)方法设置尾节点
Node pred = tail;
// 判断tail节点是否为空,不为空则添加节点到队列中
if (pred != null) {
node.prev = pred;
// CAS设置尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/**
* 插入节点到队列中
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
// 死循环 知道将节点插入到队列中为止
for (;;) {
Node t = tail;
// 如果队列为空,则首先添加一个空节点到队列中
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
// tail 不为空,则CAS设置尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
从上面源码中我们可以看到,在将节点添加到CHL尾部的时候,使用了一个CAS方法(compareAndSetTail(pred, node)),这里使用CAS的原因是防止在并发添加尾节点的时候出现线程不安全的问题(即有可能出现遗漏节点的情况)
入队列的过程:
同步队列遵循FIFO规范,首节点的线程在释放同步状态后,将会唤醒后继节点的线程,并且后继节点的线程在获取到同步状态后将会将自己设置为首节点。因为设置首节点是通过获取同步状态成功的线程来完成的,因此设置头结点的方法并不需要使用CAS来保证,因为只有一个线程能获取到同步状态。CHL出列的过程如下:
AQS 在J.U.C里面是一个非常核心的工具,而且也非常复杂,里面考虑到了非常多的逻辑实现。
ReentrantLock
ReentantLock是java中重入锁的实现,一次只能有一个线程来持有锁,包含三个内部类,Sync、NonFairSync、FairSync
构造函数如下:
//无参构造,默认使用的是非公平性锁
public ReentrantLock() {
sync = new NonfairSync();
}
//有参构造, Boolean类型的参数 true:表示公平性锁 false:非公平性锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
reentantlock是lock接口的实现类,即实现了Lock接口下所有的方法
获取锁的方法lock、trylock、lockintertuptibly加锁方式以及释放锁
公平性锁和非公平性锁的实现示例
public class NonFairAndFairDemo implements Runnable {
//静态变量,线程共享
static Integer num = 0;
//锁实例
private ReentrantLock rtl;
public NonFairAndFairDemo (ReentrantLock rtl) {
this.rtl = rtl;
}
@Override
public void run() {
while (true) {
//加锁
rtl.lock();
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
rtl.unlock();
}
}
}
public static void main(String[] args) {
//模拟两个线程不断的获取同一个共享资源,可以实现公平性锁和非公平性锁
//公平性锁
ReentrantLock fairLock = new ReentrantLock(true);
//非公平性锁
ReentrantLock noFairLock = new ReentrantLock(false);
//非公平性锁演示
Thread threadA = new Thread(new NonFairAndFairDemo(noFairLock));
threadA.setName("A");
Thread threadB = new Thread(new NonFairAndFairDemo(noFairLock));
threadB.setName("B");
threadA.start();
threadB.start();
}
公平向锁特征如上:按照线程的访问顺序进行获取
非公平性锁的特点,是每个线程都连续执行多次之后在替换成其他线程执行
公平锁和非公平锁如何实现:
公平性锁和非公平性锁的父类是sync,
Sync类是AbstractQueuedSynchronizer的子类,AQS是一个同步器,提供同步功能
abstract static class Sync extends AbstractQueuedSynchronizer
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//加锁操作,声明是抽象方法,nofairsync和fairsync中各自实现
abstract void lock();
//非公平获取,公平性锁和非公平性锁都需要这个方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//AQS获取state值
int c = getState();
if (c == 0) {
//锁空闲状态
//通过cas获取锁状态,修改state状态
if (compareAndSetState(0, acquires)) {
//标记当前线程为获取锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//锁非空闲,表明锁被占中,有一种情况,当前线程即为占用锁的线程
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//当前线程继续持有锁,仅对state进行加操作
setState(nextc);
return true;
}
return false;
}
//释放锁 sync中的tryRelease是公平性锁和非公平性锁的释放锁流程都是该方法
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);
}
setState(c);
return free;
}
//判断当前线程是否持有锁
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
//获取锁的持有者线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//加锁的次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//是否上锁 true:表示加锁
final boolean isLocked() {
return getState() != 0;
}
}
该Sync中方法的封装是调用AQS中的方法实现的
公平性锁和非公平锁的如何实现?
公平性锁:FairLock
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//同步状态为空闲,需要等待队列中第一个等待着执行
//什么时候当前线程可以执行,等待队列里没有线程等待或者是有线程等待且等待的第一个线程就是当前线程
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//当前同步状态非空闲,被线程占用且是当前线程
int nextc = c + acquires;
if (nextc < 0)
//有符号的int类型。最高位为1表示负数
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
类AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
//当前同步状态非空闲,并且是其他线程持有锁 返回false
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
公平性锁获取锁流程:
1、如果同步状态为空,就可以抢锁,能够获取锁的前提条件是当前等待队列为空,或者等待队列队头是当前线程,即当前线程才能够抢锁,通过CAS抢锁(state)抢锁成功记录当前线程信息到锁上
2、如果同步状态不为空,即存在线程占用锁且占用线程是当前线程,当前线程可成功获取锁(state)
非公平性锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//执行Lock操作,尝试立即获取锁,失败就退回常规流程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//立即获取锁失败进入到acquire,首先调用tryAcquire
}
protected final boolean tryAcquire(int acquires) {
//同步状态为空闲或者不为空闲但是是当前线程持有锁,返回true表示抢锁成功
return nonfairTryAcquire(acquires);
}
}
类AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
//当前同步状态非空闲,并且是其他线程持有锁 返回false
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
通过代码可知:很多方法,trylock,unlock都是在父类sync实现
非公平性锁抢锁流程:
1、直接通过CAS操作抢锁,如果不成功进入常规抢锁流程
2、获取当前锁的状态(state是否为0),如果为0表示空闲,直接通过CAS抢锁,如果成功,记录线程信息到锁上
3、如果锁不为空闲且是当前线程持有锁,则可直接获取锁(state+1)
公平性锁和非公平性锁不同点在于Lock方法,研究加锁流程,说明其不同点?(15分钟)
重入锁的实现:
ReentrantLock都是将具体实现委托给内部类(Sync\NonFairSync\FairSync)
ReentrantLock的重入次数是使用AQS的state属性,state大于0表示锁被占用(值表示当前线程重入次数),等于0表示锁空闲,小于0则表示重入次数太多导致溢出了
可重入锁需要一个重入计数的变量,初始值为0,当成功请求锁加1,释放锁时减1,当释放锁时计数为0则真正释放锁
重入锁必须持有对锁持有者的引用,用以判断是否可以重入。
Condition
Synchronized与wait、notify、notifyAll方法结合可以实现等待/通知模式,reentantlock同样可以实现等待、通知模式,需要借助于Condition对象,具有更好的灵活性
newCondition方法
public Condition newCondition()
Condition中提供的方法如下:
awaitXXX和Object中的wait方法类似,使当前线程进入休眠进行等待
signal和Object中的notify方法类似,唤醒一个处于休眠状态的线程
signalAll和Object总的signalAll方法类似,唤醒所有处于休眠状态的线程
生产者/消费者模型基于Condition实现
/**
* 生产者
*/
public class Consumer implements Runnable {
//仓库,容量限制为3
private LinkedList <Integer> cap;
private ReentrantLock rtl;
private Condition cTop;
private Condition pToc;
private Random random = new Random();
public Consumer(LinkedList <Integer> cap, ReentrantLock rtl, Condition cp, Condition pc) {
this.cap = cap;
this.rtl = rtl;
this.cTop = cp;
this.pToc = pc;
}
@Override
public void run() {
while (true) {
rtl.lock();
try {
//当仓库满了,需要等待,直至消费者通知
while (cap.size() == 3) {
System.out.println("仓库满了,生产者需要等待");
// cap.wait();
pToc.await();
}
//此处说明仓库不满,可以继续生产
int value = random.nextInt(1000);
//生产的数据放入仓库
cap.add(value);
System.out.println("生产者生产数据:" + value);
Thread.sleep(value);
//生成者通知消费者
cTop.signal();
// cap.notifyAll();
} catch (Exception e) {
}
//释放锁
rtl.unlock();
}
}
}
/**
* 消费者
*/
public class Producer implements Runnable {
//仓库,容量限制为3
//仓库,容量限制为3
private LinkedList <Integer> cap;
private ReentrantLock rtl;
private Condition cTop;
private Condition pToc;
private Random random = new Random();
public Producer(LinkedList <Integer> cap, ReentrantLock rtl, Condition cp, Condition pc) {
this.cap = cap;
this.rtl = rtl;
this.cTop = cp;
this.pToc = pc;
}
@Override
public void run() {
while (true) {
//仓库是作为生产者消费者共享区域,一个方数据,一个消费数据,需要互斥访问
rtl.lock();
try {
while (cap.size() == 0) {
//当前仓库为空,消费者需要等待生产者通知
System.out.println("仓库空了,消费者需要等待");
// cap.wait();
cTop.await();
}
//当前步骤表示仓库不为空,可以消费
Integer value = cap.remove();
System.out.println("消费者消费产品为:" + value);
Thread.sleep(random.nextInt(1000) + 1000);//至少休眠1秒
// 通知生产者(生产者在仓库满的情况下才会对该通知起作用,否则不起作用)
// cap.notifyAll();
//消费者单向通知生产者
pToc.signal();
} catch (Exception e) {
e.printStackTrace();
}
rtl.unlock();
}
}
}
public static void main(String[] args) {
//给定一个三个容量的仓库
LinkedList <Integer> cap = new LinkedList<>();
//加锁实例
ReentrantLock reentrantLock = new ReentrantLock();
//生产者通知消费者Condition
Condition cToP = reentrantLock.newCondition();
//消费者通知生产者Condition
Condition pToC = reentrantLock.newCondition();
new Thread(new Producer(cap,reentrantLock,cToP,pToC)).start();
new Thread(new Consumer(cap,reentrantLock,cToP,pToC)).start();
}
● 在调用Condition中的await或者是signal这些方法中任何的方法是,必须持有锁(ReentantLock),如果没有持有此锁,则抛出异常IllegalMonitorStateException
● 在调用await方法时,将释放掉锁,并在这些方法返回之前,重新先获取该锁,才能执行
● 如果线程在等待中被中断,则等待将终止,并抛出InterruptedException,清除掉中断状态
● 等待状态的线程按照FIFO顺序接收信号
● 等待方法返回的线程重新获取锁的顺序与线程最初获取锁的顺序是相同的
线程A、B、C三个线程,每个线程打印线程名,打印结果为ABCABC…每个线程打印10遍?
通过notifyall,wait 相关,通过await,sigal如何实现呢?20分
public class ABCThread extends Thread {
private String name;
private ReentrantLock rtl;
private Condition waitc;//等待Condition
private Condition sigalc; //通知Condition
public ABCThread(String name,ReentrantLock rtl,Condition wc,Condition sc){
this.name = name;
this.rtl = rtl;
this.waitc = wc;
this.sigalc = sc;
}
@Override
public void run() {
int num =0;
while (true) {
rtl.lock();
//等待其他线程通知,
try {
waitc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印当前线程信息
System.out.println(name);
//通知下一个线程
sigalc.signal();
++num;
if (num >= 10) break;
rtl.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
//A通知B
Condition ab = reentrantLock.newCondition();
//B通知C
Condition bc = reentrantLock.newCondition();
//C通知A
Condition ca = reentrantLock.newCondition();
new ABCThread("A", reentrantLock, ca, ab).start();
new ABCThread("B", reentrantLock, ab, bc).start();
new ABCThread("C", reentrantLock, bc, ca).start();
//先发起通知A线程
reentrantLock.lock();
ca.signal();
reentrantLock.unlock();
}