J.U.C 之 Condition

2020-06-22  本文已影响0人  吉他手_c156

在没有Lock之前,我们使用synchronized来控制同步,配合Object的wait()、notify()系列方法可以实现等待/通知模式。在Java SE5后,Java提供了Lock接口,相对于Synchronized而言,Lock提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活
Condition提供了一系列的方法来对阻塞和唤醒线程:

Condition是一种广义上的条件队列。他为线程提供了一种更为灵活的等待/通知模式,线程在调用await方法后执行挂起操作,直到线程等待的某个条件为真时才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现

Condition 的实现

获取一个Condition必须要通过Lock的newCondition()方法。该方法定义在接口Lock下面,返回的结果是绑定到此 Lock 实例的新 Condition 实例,Condition为一个接口,其下仅有一个实现类
ConditionObject,ConditionObject是AbstractQueuedSynchronizer的内部类,一个ConditionObject是一条等待队列

    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** 头节点 */
        private transient Node firstWaiter;
        /** 尾结点 */
        private transient Node lastWaiter;

        /**
         * Creates a new {@code ConditionObject} instance.
         */
        public ConditionObject() { }

等待

调用Condition的await()方法会使当前线程进入等待状态,同时会加入到Condition等待队列同时释放锁。当从await()方法返回时,当前线程一定是获取了Condition相关连的锁

    public final void await() throws InterruptedException {
        // 当前线程中断
        if (Thread.interrupted())
            throw new InterruptedException();
        //当前线程加入等待队列
        Node node = addConditionWaiter();
        //释放锁
        long 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);
    }

此段代码的逻辑是:首先将当前线程新建一个节点同时加入到条件队列中,然后释放当前线程持有的同步状态。然后则是不断检测该节点代表的线程释放出现在AQS同步队列中(收到signal信号之后就会在AQS队列中检测到),如果不存在则一直挂起,否则参与竞争同步状态。

        private Node addConditionWaiter() {
            // 引用尾结点
            Node t = lastWaiter;
            // 如果 t 节点的状态不为 CONDITION,则表示该节点不处于等待状态,需要清除节点
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            // 构建新的 node 节点
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            // 首次 firstWaiter 为 null ,firstWaiter 就等于当前节点
            if (t == null)
                firstWaiter = node;
            else
                // 节点长度大于 0 时,t 是尾结点的引用,设置尾结点的下一个节点为新构建的节点
                t.nextWaiter = node;
            // 设置尾节点为当前构建的节点
            lastWaiter = node;
            return node;
        }

该方法主要是将当前线程加入到Condition条件队列中。当然在加入到尾节点之前会清楚所有状态不为Condition的节点。

fullyRelease(Node node),负责释放该线程持有的锁。

    final long fullyRelease(Node node) {
        boolean failed = true;
        try {
            //节点状态--其实就是持有锁的数量
            long savedState = getState();
            //释放锁
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

isOnSyncQueue(Node node):如果一个节点刚开始在条件队列上,现在在同步队列上获取锁则返回true

    final boolean isOnSyncQueue(Node node) {
        //状态为Condition,获取前驱节点为null,返回false
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //后继节点不为null,肯定在CLH同步队列中
        if (node.next != null)
            return true;

        return findNodeFromTail(node);
    }

unlinkCancelledWaiters():负责将条件队列中状态不为Condition的节点删除

        private void unlinkCancelledWaiters() {
            // 头节点引用
            Node t = firstWaiter;
            // 定义保存 t 节点的上一个节点
            Node trail = null;
            while (t != null) {
                // 如果不为空,获取 t 节点的下一个节点
                Node next = t.nextWaiter;
                // 如果 t 节点的状态不是 CONDITION 
                if (t.waitStatus != Node.CONDITION) {
                    // 将 t 节点的下一个节点设置为空
                    t.nextWaiter = null;
                    如果当前节点没有上一个节点,将头节点也指向 t 的 next 节点
                    if (trail == null)
                        firstWaiter = next;
                    else
                        // 如果有上一个节点,由于上一个节点的下一个节点原本指向的是 t 当前节点,
                        // 由于要清除当前节点,所以让 trail 的下一个节点指向 t 节点的 next 节点
                        trail.nextWaiter = next;
                     // 如果没有下一个节点,将尾结点也指向 trail 
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    // 将 t 赋值给 trail 也就是 t 的上一个节点
                    trail = t;
                // 继续便利 t 的下一个节点
                t = next;
            }
        }

唤醒

调用Condition的signal()方法,将会唤醒在等待队列中等待最长时间的节点(条件队列里的首节点),在唤醒节点前,会将节点移到AQS同步队列中

    public final void signal() {
        //检测当前线程是否为拥有锁的独
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        //头节点,唤醒条件队列中的第一个节点
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);    //唤醒
    }

该方法首先会判断当前线程是否已经获得了锁,这是前置条件。然后唤醒条件队列中的头节点。
doSignal(Node first):唤醒头节点

    private void doSignal(Node first) {
        do {
            //修改头结点,完成旧头结点的移出工作
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                (first = firstWaiter) != null);
    }

doSignal(Node first)主要是做两件事:1.修改头节点,2.调用transferForSignal(Node first) 方法将节点移动到AQS同步队列中。transferForSignal(Node first)源码如下:

     final boolean transferForSignal(Node node) {
        //将该节点从状态CONDITION改变为初始状态0,
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //将节点加入到syn队列中去,返回的是syn队列中node节点前面的一个节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //如果结点p的状态为cancel 或者修改waitStatus失败,则直接唤醒
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

整个通知的流程如下:

总结

一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到条件队列中,然后释放锁,最后通过isOnSyncQueue(Node node)方法不断自检看节点是否已经在CLH同步队列了,如果是则尝试获取锁,否则一直挂起。当线程调用signal()方法后,程序首先检查当前线程是否获取了锁,然后通过doSignal(Node first)方法唤醒CLH同步队列的首节点。被唤醒的线程,将从await()方法中的while循环中退出来,然后调用acquireQueued()方法竞争同步状态。
Condition是个接口,基本的方法就是await()和signal()方法;

Condition 基本使用

Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()。

使用 Condition 模拟生产者消费者简单示例

构建生产者

/**
 *
 * 生产者生产消息
 */
public class Product implements Runnable{

    private Lock lock;
    private Condition condition;
    private Queue<String> msg;
    private int maxSize;

    public Product(Lock lock, Condition condition, Queue<String> msg, int maxSize) {
        this.lock = lock;
        this.condition = condition;
        this.msg = msg;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        int i = 0;
        while (true){
            try {
                lock.lock();
                // 如果队列元素已经满了
                if(maxSize == msg.size()){
                    try {
                        condition.await();  // 队列满了阻塞队列
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int a = i++;
                msg.add(i+"");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产消息:"+a);
                // 唤醒消费者
                condition.signal();
            }finally {
                lock.unlock();
            }
        }
    }
}

构建消费者

/**
 * 消费者
 */
public class Consumer implements Runnable {

    private Lock lock;
    private Condition condition;
    private Queue<String> msg;

    public Consumer(Lock lock, Condition condition, Queue<String> msg) {
        this.lock = lock;
        this.condition = condition;
        this.msg = msg;
    }

    @Override
    public void run() {
        while (true){
            try{
                lock.lock();
                // 如果队列中没有元素了
                if(msg.size() == 0){
                    // 阻塞线程
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费消息="+msg.remove());
                // 唤醒生产者
                condition.signal();
            }finally {
                lock.unlock();
            }
        }
    }
}

测试

public class MainClass {

    public static void main(String[] args) {
        // 构建重入锁 默认非公平锁
        Lock lock = new ReentrantLock();
        // 获取 Condition
        Condition condition = lock.newCondition();
        // 构建对列
        Queue<String> msg = new LinkedList<String>();
        // 设置队列最大长度
        int maxSize = 5;
        // 构建生产者
        Product p = new Product(lock,condition,msg,maxSize);
        // 构建消费者
        Consumer c = new Consumer(lock,condition,msg);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}

结果

生产消息:0
消费消息=1
生产消息:1
生产消息:2
生产消息:3
生产消息:4
生产消息:5
消费消息=2
生产消息:6
消费消息=3
生产消息:7
消费消息=4
生产消息:8
消费消息=5
生产消息:9
消费消息=6
生产消息:10
......
上一篇下一篇

猜你喜欢

热点阅读