Lock和ReentrantLock

JDK1.5中提供了锁的接口:java.util.concrrent.locks.lock,提供了ReentrantLock,ReentrankReadWriteLock实现类

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(AbstractQueueSynchronizer)是J.U.C下较复杂的一个类,提供了一个为实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供了一个框架。

通过上面查看类的子类的层级关系可知:AQS是CountdownLatch、ReentrankLock、threadPoolExecutor、ReentrantReadWriterLock和Semaphore实现的基础。

AQS存在的必要性

上图中体现AQS很重要的概念:“阻塞队列”,当线程无法获取资源时,提供一个FIFO类型的有序队列,用于维护所有处于“等待中”的线程

AQS核心字段:

主要有三个核心字段:

private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;

其中state是描述有多少个线程获取锁

state=0:表示锁第空闲状态
state>0:表示锁被占用
state<0:表示溢出

state在AQS中是一个很重要的属性,不管是独占锁还是共享锁,通过CAS操作该字段,

如果是独占锁,需要通过CAS将其从0变为1,标记加锁成功

如果是共享锁,表示的是并发的线程数

head和tail加上CAS就构成了一个FIFO队列,是一个双向链表,每个节点是Node类型,通过双向队列来完成同步状态的管理,如果当前线程获取同步状态失败,AQS将会把当前线程以及等待的状态信息构建成一个节点Node并加入到同步队列,同时会阻塞当前线程,当同步状态释放掉,会将处于队列首节点的线程唤醒,让该线程获取同步状态,在队列中节点用来保存同步状态的线程(thread)、等待状态(waitStatus)、前驱节点(prev)和后继节点(next)

 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,新生的非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;

要支持上面两个操作就必须有下面的条件:

  1. 原子性操作同步器的状态位state->CAS
  2. 阻塞和唤醒线程
  3. 一个有序的队列

目标明确,要解决的问题也清晰了,那么剩下的就是解决上面三个问题

状态位的原子操作

这里使用一个32位的整数来描述状态位,使用CAS操作来修改状态。事实上这里还有一个64版本的同步器,这里暂且不谈。

阻塞和唤醒线程

标准的JAVA API里面是无法挂起(阻塞)一个线程,然后在将来某个时刻再唤醒它。

在JDK1.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()返回的原因有三:

  1. 其他某个线程调用将当前线程作为目标调用unpark;
  2. 其他某个线程中断当前线程
  3. 该调用不合逻辑的返回

其实第三条就决定了需要循环检测,类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。

有序队列

在AQS中采用CHL列表来解决有序的队列问题

 AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。

查看类的方法和属性

快捷键:comand(win)+7

非快捷键:类的顶部栏:View->Tool Windows->Structure

对于入队列:从数据结构上出发,入队列是比较简单的,无非就是当前队列中的尾结点指向新节点,新节点的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

ReentrantLock是java中重入锁的实现,一次只能有一个线程来持有锁,包含三个内部类,Sync、NonFairSync、FairSync

构造函数如下:

   //无参构造,默认使用的是非公平性锁 
   public ReentrantLock() {
        sync = new NonfairSync();
    }
   //有参构造, Boolean类型的参数 true:表示公平性锁  false:非公平性锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock是Lock接口的实现类,实现了Lock接口下的所有方法

获取锁的方法lock、trylock、lockinterruptibly加锁方式以及释放锁

公平性锁和非公平性锁的实现示例

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类是AbstractQueuedSynchoronizer的子类,AQS是一个同步器,提供同步功能

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) {
                //同步状态为空闲,需要等待队列中第一个等待着执行
                //什么时候当前线程可以执行,等待队列里没有线程等待或者是有线程等待且等待的第一个线程就是当前线程
//hasQueuedPredecessors()主要是用来判断线程需不需要排队,因为队列是FIFO的,所以需要判断队列中有没有相关线程的节点已经在排队了。有则返回true表示线程需要排队,没有则返回false则表示线程无需排队。
                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抢锁,抢锁成功并记录当前线程信息到锁上

2、如果同步状态不为空,即存在线程占用锁并且占用线程是当前线程,当前线程可成功获取锁

非公平性锁

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)

重入锁的实现:

ReentrantLock都是将具体实现委托给内部类(Sync\NonFairSync\FairSync)

ReentrantLock的重入次数是使用AQS的state属性,state大于0表示锁被占用(值表示当前线程重入次数),等于0表示空闲,小于0表示重入次数太多导致溢出

可重入锁需要一个重入计数的变量,初始值为0,当成功抢锁+1,释放锁时减1,当释放锁时计数为0则真正释放锁

重入锁必须持有对锁持有者的引用,用以判断是否可以重入

Condition

Synchronized与wait、notify、notifyall方法结合可以实现等待、通知模式,reentrantLock同样了以实现等待、通知模式,需要借助于Condition对象,具有更好的灵活性

public Condition newCondition()

Condition中提供的方法如下:

 awaitXXX和Object中的wait方法类似,使当前线程进入休眠进行等待

signal和Object中的notify方法类似,唤醒一个处于休眠状态的线程

signalAll和Object中的notifyAll方法类似,唤醒所有处于休眠状态的线程

生产者/消费者模型基于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这些方法中的任何方法是必须持有锁(ReentrantLock),如果没有持有锁,则抛出异常IlegalMonitorStateException

在调用await方法时,将释放掉锁,并在这些方法返回之前,重新先获取该锁,才能执行

如果线程在等待中被中断,则等待将停止,并抛出InterruptedException,清除掉中断状态

等待状态的线程按照FIFO顺序接收信号

等待方法返回的线程重新获取锁的顺序与线程最初获取锁的顺序是相同的

线程A、B、C三个线程,每个线程打印线程名,打印结果为ABCABC...每个线程打印十遍

通过await、signal实现:

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();




    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值