6.并发类Condition源码分析

并发编程学习目录

并发编程(1)-java中的6中线程状态
并发编程(2)-怎么中断线程?
并发编程(3)-synchronized的实现原理
并发编程(4)-深入理解volatile关键字
并发编程(5)-ReentrantLock源码分析

一、用ReentrantLock加condition完成生产消费者模型

在前面学习 synchronized 的时候,有讲到 wait/notify 的基本使用,结合 synchronized 可以实现对线程的通信。那么这个时候我就在思考了,既然 J.U.C 里面提供了锁的实现机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢? 于是找阿找,发现了一个Condition 工具类。Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒。
condition基本使用:
生产者:

package com.gupao.condition;

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

public class Product implements Runnable {

    private Queue<Integer> queue;

    private Lock lock;

    private Condition condition;

    private int maxSize;

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

    @Override
    public void run() {

        int i = 0;
        while (true) {
            i++;
            lock.lock();
            while (queue.size() == maxSize) {
                try {
                    System.out.println("队列满了!");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            queue.add(i);
            System.out.println("生产者生产消息" + i);
            condition.signal();
            lock.unlock();
        }

    }
}

消费者:

package com.gupao.condition;

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

public class Customer implements Runnable {

    // 对列
    private Queue<Integer> queue;

    // 锁
    private Lock lock;

    private Condition condition;

    // 队列的大小
    private int maxSize;

    public Customer(Queue<Integer> queue, Lock lock, Condition condition, int maxSize) {
        this.queue = queue;
        this.lock = lock;
        this.condition = condition;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            i++;
            lock.lock();
            while (queue.size() == 0) {
                try {
                    System.out.println("队列空了!");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Integer remove = queue.remove();
            System.out.println("消费者消费消息" + remove);
            condition.signal();
            lock.unlock();
        }
    }
}

测试类:

	public class main {

    public static void main(String[] args) throws InterruptedException {
        Queue<Integer> queue = new LinkedBlockingQueue<>();
        int maxSize = 5;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Thread t1 = new Thread(new Product(queue,lock,condition,maxSize));
        Thread t2 = new Thread(new Customer(queue, lock, condition, maxSize));

        t2.start();
        Thread.sleep(500);
        t1.start();
    }
}

二、源码分析

该方法是建立在已经拿到锁的情况下。想了解lock的原理的可以看我另外一篇博客
ReentrantLock原理分析
调用Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列,在上面那个案例中,假如两个线程同时运行的话,那么 AQS 的队列可能是下面这种情况
在这里插入图片描述
调用Condition 的await()方法(或者以 await 开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。

public final void await() throws InterruptedException {
			// 该方法允许被中断
            if (Thread.interrupted())
                throw new InterruptedException(); 
            // 创建一个新的节点加入condition队列中,节点状态为condition
            Node node = addConditionWaiter();
            // 释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
            while (!isOnSyncQueue(node)) {
            // 判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了
            // 通过 park 挂起当前线程
            // 上下文切换(计数器、寄存器) 挂起保存,唤醒的时候恢复
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 当这个线程醒来,会尝试拿锁, 当acquireQueued返回 false 就是拿到锁了.
			// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了.
			// 将这个变量设置成 REINTERRUPT.
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            // 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点.
			// 如果是 null ,就没有什么好清理的了.
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            // 如果线程被中断了,需要抛出异常.或者什么都不做
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

addConditionWaiter
这个方法的主要作用是把当前线程封装成 Node,添加到等待队列。这里的队列不再是双向链表,而是单向链表

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            // 如果最后一个节点不等于condtion状态,则从链表中删除,不是condition状态就是cancelled状态
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            // 创建一个新的节点,状态是condition,加入到链表中,这里是单项链表
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

unlinkCancelledWaiters
这个方法是清理掉链表中的状态为cancelled状态的节点

 private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            // 如果首节点不为空
            while (t != null) {
            	// 获取到下个节点
                Node next = t.nextWaiter;
                // 如果该节点的状态不等于conditon,则该节点需要在链表中删除
                if (t.waitStatus != Node.CONDITION) {
                	// 该节点的下个节点设置为空,意味着垃圾回收后就回收该节点
                    t.nextWaiter = null;
                    // trail 为空,则把下一个节点负责给首节点
                    if (trail == null)
                        firstWaiter = next;
                    else
                    	// 把下一个节点赋值给next,这样链表就要继续连接起来
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                // 等于condtion,把该节点赋值给尾节点
                else
                    trail = t;
                // 下个一个节点赋值给t,进行下一次循环
                t = next;
            }
        }

图解分析
执行完addConditionWaiter 这个方法之后,就会产生一个这样的condition 队列
在这里插入图片描述
fullyRelease
fullRelease,就是彻底的释放锁,什么叫彻底呢,就是如果当前锁存在多次重入,那么在这个方法中只需要释放一次就会把所有的重入次数归零。

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 获得了锁。
在这里插入图片描述
isOnSyncQueue
判断当前节点是否在同步队列中,返回 false 表示不在,返回true 表示如果不在,
讲当前线程挂起

final boolean isOnSyncQueue(Node node) {
		// 如果状态是condition,证明一定不再同步队列里,condition状态只存在于等待队列,在同步队列里,node.prev是一定不为空的,因为有个head的节点
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 在等待队列里,node.next 是等于空的,不等于空就是在同步队列当中
        if (node.next != null) // If has successor, it must be on queue
            return true;
       // 遍历正个同步队列,判断node是否在同步队列当中
        return findNodeFromTail(node);
    }

await 方法会阻塞 ThreadA,然后 ThreadB 抢占到了锁获得了执行权限,这个时候在 ThreadB 中调用了 Condition 的 signal()方法,将会唤醒在等待队列中节点

public final void signal() {
	// 判断当前线程是否获取到了锁,如果没有抛异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            // 如果首节点不为空,唤醒首节点
            if (first != null)
                doSignal(first);
        }

doSignal

private void doSignal(Node first) {
            do {
            // frist的下一个节点如果为空,就把lastWaiter设置为空
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
             // 不为空,再把first 节点从等待队列中移除
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
            	// 返回false,firstWaiter已经被从新赋值过了,如果不是空,进行下一次遍历
                     (first = firstWaiter) != null);
        }

transferForSignal

final boolean transferForSignal(Node node) {
       // 如果该节点不是condition状态(可能编程了cancelled状态),waitStatus=0,就会设置失败,返回false
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
	// 将该节点放入到AQS队列中,返回上一个节点
        Node p = enq(node);
        int ws = p.waitStatus;
        // 如果上一个节点状态没有被改变,也就是没有编程cancelled状态,就将该节点状态设置成singal状态
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 如果状态已经是cancelled状态,将该节点的线程挂起
            LockSupport.unpark(node.thread);
        return true;
    }

被阻塞的线程唤醒后的逻辑

前面在分析await 方法时,线程会被阻塞。而通过 signal被唤醒之后又继续回到上次执行的逻辑中

		while (!isOnSyncQueue(node)) {
           LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
        }

checkInterruptWhileWaiting

 private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

transferAfterCancelledWait
进入到该方法,证明该线程被中断过。
这里有个知识点,就是进入该线程进入该方法,不一定是被signal唤醒点,也有可能是是调用了该线程的interrupt()方法,这个方法会更新一个中断标识,并且会唤醒处于阻塞状态下的线程。

final boolean transferAfterCancelledWait(Node node) {
		// 如果cas成功,代表是被线程中断的,放入到AQS队列,返回true
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        // 如果cas 失败,返回false
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

如果返回true,证明这个没有执行被signal唤醒,就被中断了,checkInterruptWhileWaiting返回THROW_IE
如果返回false, 证明这个这个方法是被唤醒以后,在被中断的
checkInterruptWhileWaiting返回REINTERRUPT

reportInterruptAfterWait
如果是THROW_IE,则抛出中断异常
如果是REINTERRUPT,则重新响应中断

 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            // 如果是被中断唤醒的,线程直接抛异常
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            // 如果是被
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值