JUC如是实现线程之间的通信

1. Condition 组件

在jdk中,synchronized可以实现线程通信,wait/notify 方法;在Condition中提供了多线程协调的通信,可以让某一个线程等待,只有条件满足才能被唤醒;

1.1 Condition使用

package com.pattern.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * Created by chenli on 2019/10/29.
 */
public class ConditionWaitDemo extends Thread {
    private Lock lock;
    private Condition condition;

    public ConditionWaitDemo(Lock lock,Condition condition){
        this.lock=lock;
        this.condition=condition;
    }

    @Override
    public void run() {
        lock.lock();
        System.out.println("Thread1 start await");
        try {
            condition.await();
            System.out.println("Thread1 end await");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

singal

package com.pattern.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * Created by chenli on 2019/10/29.
 */
public class ConditionNotifyDemo extends Thread {
    private Lock lock;
    private Condition condition;

    public ConditionNotifyDemo(Lock lock,Condition condition){
        this.lock=lock;
        this.condition=condition;
    }

    @Override
    public void run() {
        lock.lock();
        System.out.println("Thread2 start signal");
        try {
            condition.signal();
            System.out.println("Thread2 end signal");
        } finally {
            lock.unlock();
        }
    }
}

testDemo

package com.pattern.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by chenli on 2019/10/29.
 */
public class ConditionDemo {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
        Condition condition=lock.newCondition();
        new Thread(new ConditionWaitDemo(lock,condition)).start();
        new Thread(new ConditionNotifyDemo(lock,condition)).start();


    }
}

当调用await方法时,当前线程会阻塞,并且释放锁;condition 对象的 signal 或者 signalall 方法通知并被阻塞 的线程,然后自己执行unlock释放锁,被唤醒的线程获得 之前的锁继续执行,最后释放锁。

1.2Condition源码解析

调用condition需要获取锁,存在一个AQS同步队列,如果两个线程同时运行,则AQS队列中
在这里插入图片描述
这个时候线程A调用condition.await方法,会使当前线程加入到等待队列中,并且释放锁,同时线程的状态变为等待状态,

1.1.1 condition.await源码

public final void await() throws InterruptedException {
            if (Thread.interrupted())//表示await线程允许被中断
                throw new InterruptedException();
            Node node = addConditionWaiter();//创建一个新的节点,condition node
            int savedState = fullyRelease(node);//释放锁,获取锁的状态,并且唤醒AQS队列中的一个线程
            int interruptMode = 0; //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞 
            while (!isOnSyncQueue(node)) {//判断该节点有没有在AQS对类中,没有则挂起
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //当线程唤醒后会尝试获取锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            // interruptMode != THROW_IE -> 表示这个线程
没有成功将 node 入队,但 signal 执行了 enq 方法让其入
队了. 
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

1.1.2 addConditionWaiter

这个方式时把该线程封装成node 放到等待队列中,单项链表

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

1.1.3 图解

在这里插入图片描述

1.3 fullyRelease

fullyRelease 是彻底的释放锁,如果当前是重入锁,则需要 释放一次;

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {//释放锁,唤醒下一个同步队列中的节点
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

此时同步队列会触发释放锁和重新竞争锁,ThreadB获取锁
在这里插入图片描述

1.4 isOnSyncQueue

isOnSyncQueue 判断当前节点是否在同步队列中,如果当前线程没有同步队列中,说明当前节点没有唤醒去争抢同步锁,需要把当前线程挂机来,知道其他线程调用signal方法;
如果在AQS同步队列,意味着它需要去竞争同步锁去获得 执行程序执行权限 为什么要做这个判断呢?原因是在 condition 队列中的节 点会重新加入到AQS队列去竞争锁。也就是当调用signal 的时候,会把当前节点从condition队列转移到AQS队列

1.4.1 如何判断线程A是否在同步队列中?
  1. 如果ThreadA的waitStatus的状态为CONDITION,说 明它存在于 condition 队列中,不在 AQS 队列。因为 AQS队列的状态一定不可能有CONDITION
  2. 如果node.prev为空,说明也不存在于AQS队列,原因 是prev=null在AQS队列中只有一种可能性,就是它是 head节点,head节点意味着它是获得锁的节点。
  3. 如果 node.next 不等于空,说明一定存在于 AQS 队列 中,因为只有AQS队列才会存在next和prev的关系
  4. findNodeFromTail,表示从tail节点往前扫描AQS队列,
    一旦发现AQS队列的节点和当前节点相等,说明节点一 定存在于AQS队列中
final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }

1.5 condition.signal

线程B获取了抢占锁的权限,这个时候在 ThreadB 中调用了 Condition 的signal()方法,将会唤醒在等待队列中节点

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

1.5.1 doSignal

从condition队列中的首部开始第一个condition,执行transferForSignal操作,将节点从等待队列转移到AQS队列中,同时修改 AQS 队列中原先尾 节点的状态

private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

1.5.2 transferForSignal方法

该方法进行cas修改节点操作,,如果成功,就将这个节 点放到 AQS 队列中,然后唤醒这个节点上的线程。此时, 那个节点就会在 await 方法中苏醒

final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);;//调用 enq,把当前节点添加到
AQS 队列。并且返回返回按当前节点的上一个节点,也就是原
tail 节点
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true; //如果 node 的 prev 节点已经是
signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS 队
列来完成
    }

checkInterruptWhileWaiting

线程A回复执行后

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            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);
        }
        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

如果当前线程被中断,则调用 transferAfterCancelledWait方法判断后续的处理应该是 抛出InterruptedException还是重新中断。

这里需要注意的地方是,如果第一次CAS失败了,则不 能判断当前线程是先进行了中断还是先进行了signal方法 的调用,可能是先执行了signal然后中断,也可能是先执 行了中断,后执行了signal,当然,这两个操作肯定是发 生在CAS之前。这时需要做的就是等待当前线程的node 被添加到AQS队列后,也就是enq方法返回后,返回 false告诉checkInterruptWhileWaiting方法返回 REINTERRUPT(1),后续进行重新中断。
简单来说,该方法的返回值代表当前线程是否在park的 时候被中断唤醒,如果为true表示中断在signal调用之 前,signal还未执行,那么这个时候会根据await的语 义,在await时遇到中断需要抛出 interruptedException,返回true就是告诉 checkInterruptWhileWaiting返回THROW_IE(-1)。 如果返回false,否则表示signal已经执行过了,只需要 重新响应中断即可

总结

Condition 总结
await 和 signal 的总结 ,把前面的整个分解的图再通过一张整体的结构图来表 述,线程awaitThread先通过lock.lock()方法获取锁成功 后调用了condition.await方法进入等待队列,而另一个 线程signalThread通过lock.lock()方法获取锁成功后调用 了condition.signal或者signalAll方法,使得线程 awaitThread能够有机会移入到同步队列中,当其他线程 释放lock后使得线程awaitThread能够有机会获取 lock,从而使得线程awaitThread能够从await方法中退 出执行后续操作。如果awaitThread获取lock失败会直 接进入到同步队列。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值