并发编程六、J.U.C并发工具及原理分析 01

一、线程通信 Condition

  在使用synchronized时,我们结合wait、notify、notifyAll可以实现线程的通信,其中wait方法会阻塞当前线程并释放锁,notify/notifyAll会唤醒等待锁的线程并抢占锁。JUC中也为我们提供了一个多线程协调通信的工具类Condition,可以让竞争同一把锁的多个线程之间互相唤醒通信。
java.util.concurrent.locks.Condition#await()方法和java.lang.Object#wait()方法相似,会阻塞当前线程并释放锁;
java.util.concurrent.locks.Condition#signal、java.util.concurrent.locks.Condition#signalAlljava.lang.Object#notify、java.lang.Object#notifyAll方法相似,会唤醒等待锁的线程并去抢占锁。

1. Condition基本使用

  我们会以生产者、消费者为例实现线程的互相通信。本例中会创建生产者、消费者两个线程,两者公用同一个队列、同一把锁。当生产者生产数据并将队列填充满时,会挂起并通知消费者来消费数据;当消费者将队列中数据消费完毕,会挂起消费者线程并唤醒生产者来生产,依次循环。
实现逻辑与之前synchronized、wait、notify的例子一致:Synchronized 线程通信
生产者 : 负责生产数据添加至队列中去并通知消费者,当队列已满则会停止生产挂起线程并释放锁,此时之前通知的线程会去竞争锁

import java.io.Serializable;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class Producer extends Thread implements Serializable {

    Lock lock;
    Condition condition;
    Queue<String> queue;
    int maxSize;
    int i = 0;

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

    @Override
    public void run() {
        lock.lock();
        try {
            while (true) {
                i++;

                if (queue.size() == maxSize) {
                    // 队列已满,生产者阻塞,释放锁、唤醒其余竞争该锁的线程 即 消费者线程
                    System.out.println("队列已满,Producer 生产者阻塞并释放锁");
                    condition.await();
                }

                String msg = "Producer 生产消息 " + i;
                System.out.println(msg);
                queue.add(msg);
                // 通知消费者,但是此时锁未释放,所以其余线程并不能成功抢占锁;只有在await释放锁时消费者才能抢占
                condition.signal();
//                condition.signalAll();
                TimeUnit.SECONDS.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 除非出异常,否则不会调用这里的unlock来释放锁
            System.out.println("Producer 释放锁");
            lock.unlock();
        }

    }

}

消费者 :负责从队列中取出消费来消费,当队列为空则挂起当前线程并释放锁

import java.io.Serializable;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class Consumer extends Thread implements Serializable {

    Lock lock;
    Condition condition;
    Queue<String> queue;
    int maxSize;

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

    @Override
    public void run() {

        lock.lock();
        try {
            while (true) {

                if(queue.isEmpty()) {
                    // 队列为空,消费者阻塞,释放锁,唤醒其余竞争该锁的线程 即唤醒生产者线程
                    System.out.println("队列为空,Consumer 消费者阻塞并释放锁");
                    condition.await();
                }

                String msg = queue.poll();
                System.out.println("Consumer 消费消息: " + msg);
                // 通知
                condition.signal();
                TimeUnit.SECONDS.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 除非出异常,否则不会调用这里的unlock来释放锁
            System.out.println("Consumer 释放锁");
            lock.unlock();
        }

    }

}

测试类

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class MainTest {

    public static void main(String[] args) {

        // 生产者、消费者使用同一把锁
        ReentrantLock lock = new ReentrantLock();
        // 同一个condition条件
        Condition condition = lock.newCondition();
        Queue queue = new LinkedList();
        int maxSize = 5;

        Producer producer = new Producer(lock, condition, queue, maxSize);
        Consumer consumer = new Consumer(lock, condition, queue, maxSize);

        producer.start();
        consumer.start();

    }

}

...运行结果
队列为空,Consumer 消费者阻塞并释放锁
Producer 生产消息 1
Producer 生产消息 2
Producer 生产消息 3
Producer 生产消息 4
Producer 生产消息 5
队列已满,Producer 生产者阻塞并释放锁
Consumer 消费消息: Producer 生产消息 1
Consumer 消费消息: Producer 生产消息 2
Consumer 消费消息: Producer 生产消息 3
Consumer 消费消息: Producer 生产消息 4
Consumer 消费消息: Producer 生产消息 5
队列为空,Consumer 消费者阻塞并释放锁
Producer 生产消息 6
Producer 生产消息 7
Producer 生产消息 8
Producer 生产消息 9
Producer 生产消息 10
队列已满,Producer 生产者阻塞并释放锁
Consumer 消费消息: Producer 生产消息 6
Consumer 消费消息: Producer 生产消息 7
Consumer 消费消息: Producer 生产消息 8
...
...

2. Condition队列模型

Condition等待队列基本结构

AQS同步队列与Codnition等待队列

3. Condition源码分析

  分析源码之前,首先理一下基本概念:
Condition是基于JUC内Lock创建的,实现Lock的核心组件是AQS(AbstractQueuedSynchronizer),本质上锁的竞争是对一个共享变量state的修改,没有获得锁的线程封装为Node存储于AQS的一个双向链表的同步队列里;
Condition.await()会做四件事情:a.将当前线程封装为Condition类型节点并存放于Condition队列、b.释放锁、c.从AQS队列中唤醒等待的线程、d.阻塞当前线程;
Condition.signal()会将 Condition队列 中第一个节点firstWaiter从 Condition队列 转移至 AQS同步队列,但是并不会唤醒线程;
在其它线程释放锁后,AQS会从 AQS同步队列 中查找合适的head节点并唤醒;

AQS同步队列  :  一个双向链表,节点为Node{prev, next, thread},存放没有竞争到锁的线程
Condition等待队列 :  一个单向链表,节点也为Node{nextWaiter, thread}, 存放condition.await()的线程 

结合上面生产者和消费者例子来对每个步骤进行源码分析:

  1. 首先main方法中Producer和Consumer线程同时启动,所以可能是其中任何一个线程会先获得锁,根据上面运行结果很明显是Consumer线程先获得了锁;

  2. Consumer此时获取了锁,则Producer竞争锁失败,Producer线程封装为Node节点放入ReentrantLock的AQS同步队列中并阻塞;

  3. Consumer中发现queue为空,没有消费数据所以调用await, await会 a.将当前线程封装为Condition类型节点并存放于Condition队列、b.释放锁、c.从AQS队列中唤醒等待的线程、d.阻塞当前线程;

consumer 
	if(queue.isEmpty()) {
		// 队列为空,消费者阻塞,释放锁,唤醒其余竞争该锁的线程 即唤醒生产者线程
		System.out.println("队列为空,Consumer 消费者阻塞并释放锁");
		condition.await();
	}

await源码:

	
	public final void await() throws InterruptedException {
		if (Thread.interrupted())	// 线程是否被中断过  false
			throw new InterruptedException();
		Node node = addConditionWaiter(); // a.将当前线程封装为Condition类型节点并存放于Condition队列
		int savedState = fullyRelease(node); // b.释放锁、c.从AQS队列中唤醒等待的线程
		int interruptMode = 0;
		while (!isOnSyncQueue(node)) { // 当前节点是否在AQS同步队列中, 明显不在
			LockSupport.park(this); // d.阻塞当前线程;     至此,await中consumer线程完成了a、b、c、d四个步骤
			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);
	}
	
	...
	// a. 封装为一个Condition节点并加入Condition队列中去
	private Node addConditionWaiter() {
		Node t = lastWaiter;
		// If lastWaiter is cancelled, clean out.
		if (t != null && t.waitStatus != Node.CONDITION) { // 此时lastWaiter为null
			unlinkCancelledWaiters(); // 这里如果 lastWaiter.waitStatus不是CONDITION状态,表示该线程被中断过Thread.interrupter(),需要清除Condition队列中的脏数据
			t = lastWaiter;
		}
		Node node = new Node(Thread.currentThread(), Node.CONDITION); // 封装producer为Condition节点
		if (t == null)
			firstWaiter = node; // 指定当前节点为 firstWaiter
		else
			t.nextWaiter = node;
		lastWaiter = node; // 新加的节点默认为 lastWaiter
		return node;
	}
	...
    // b.释放这个线程的全部重入锁  c.唤醒等待的线程
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState(); // 获取重入次数
            if (release(savedState)) { // 释放锁, 重置AQS的state状态为0,并将占有线程置为null, 
                failed = false;
                return savedState;  // 返回重入次数
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
	//  b.释放这个线程的全部重入锁  c.唤醒等待的线程
    public final boolean release(int arg) {
        if (tryRelease(arg)) { // 此时state为0,可以释放锁,
            Node h = head; // 拿到AQS队列head节点 ,此时head为一个new Node()默认节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);  // c. 唤醒节点
            return true;
        }
        return false;
    }
	// b. 释放锁 
	protected final boolean tryRelease(int releases) {
		int c = getState() - releases;  // 相减为0
		if (Thread.currentThread() != getExclusiveOwnerThread())  
			throw new IllegalMonitorStateException();
		boolean free = false;
		if (c == 0) {
			free = true; 
			setExclusiveOwnerThread(null);  // 重置占有锁的线程为null 
		}
		setState(c); // 重置state为0,也就是释放了锁 
		return free;
	}

    // c. 唤醒节点 
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0) // 此时head.waitStatus 为0
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next; // next节点即为之前阻塞的producer节点
        if (s == null || s.waitStatus > 0) { //  s.waitStatus=-1
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null) 
            LockSupport.unpark(s.thread);  // 唤醒producer线程
    }
	...
	// 是否在同步队列中 false 
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null) // 此时node为CONDITION节点
            return false; // 直接返回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);
    }
	...
        d.阻塞当前线程
	LockSupport.park(this); // 挂起线程 
	
	//至此,await中consumer线程完成了:  将consumer线程封装为Node存入Condition队列中、释放了锁、唤醒了producer线程、并将当前consumer线程阻塞

4.producer线程被唤醒并获得锁,判断队列未满,生产消息并signal通知Consumer条件已满足
在Consumer的await()内,已经将锁释放并将producer线程唤醒,则producer线程从AQS的之前park的逻辑开始执行
首先是在lock()中被阻塞的

producer
    @Override
    public void run() {
        lock.lock();
        try {
            while (true) {
				...
			}
		...
	}		

lock()被阻塞的源码,现在重新唤醒来抢占锁

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);   // 第3步中unpark唤醒,继续执行
        return Thread.interrupted();  // producer线程没有被中断过,返回false
    }
	
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();   // AQS同步队列中 producer的上一节点
                if (p == head && tryAcquire(arg)) { // 此时可以成功抢夺锁
                    setHead(node);  // 把自身,即producer置为head节点
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;  // 至此返回, consumer线程继续 lock.lock()的后续逻辑
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
	
	
	protected final boolean tryAcquire(int acquires) {
		return nonfairTryAcquire(acquires);
	}
	
	// 非公平锁 抢占锁
	final boolean nonfairTryAcquire(int acquires) {
		final Thread current = Thread.currentThread();
		int c = getState();
		if (c == 0) {
			if (compareAndSetState(0, acquires)) { // state 0 -> 1
				setExclusiveOwnerThread(current);  // 标记占有线程
				return true;
			}
		}
		else if (current == getExclusiveOwnerThread()) {
			int nextc = c + acquires;
			if (nextc < 0) // overflow
				throw new Error("Maximum lock count exceeded");
			setState(nextc);
			return true;
		}
		return false;
	}
	

producer线程继续 lock.lock()的后续逻辑,队列未满继续生产、并signal通知consumer线程 将consumer线程从Condition队列转移至AQS的同步队列中

    @Override
    public void run() {
        lock.lock();
        try {
            while (true) {
                i++;

                if (queue.size() == maxSize) {
                    // 队列已满,生产者阻塞,释放锁、唤醒其余竞争该锁的线程 即 消费者线程
                    System.out.println("队列已满,Producer 生产者阻塞并释放锁");
                    condition.await();
                }

                String msg = "Producer 生产消息 " + i;
                System.out.println(msg);
                queue.add(msg);
                // 通知消费者,但是此时锁未释放,所以其余线程并不能成功抢占锁;只有在await释放锁时消费者才能抢占
                condition.signal();
//                condition.signalAll();
                TimeUnit.SECONDS.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 除非出异常,否则不会调用这里的unlock来释放锁
            System.out.println("Producer 释放锁");
            lock.unlock();
        }

    }

在将消息加入到队列后,会调用condition.signal();通知到condition队列:

signal源码:

	// 通知Condition对象
	public final void signal() {
		if (!isHeldExclusively()) // 当前线程是否持有排他锁, 是的, !返回false
			throw new IllegalMonitorStateException();
		Node first = firstWaiter;  // 这里的firstWaiter即为consumer节点,因为consumer线程调用await()时已把自己加入到Condition队列中去
		if (first != null)
			doSignal(first); // 通知firstWaiter节点
	}
	
	// 是否持有排他锁
	protected final boolean isHeldExclusively() {
		// While we must in general read state before owner,
		// we don't need to do so to check if current thread is owner
		return getExclusiveOwnerThread() == Thread.currentThread();  // 此时AQS中锁是producer持有,返回true
	}
		
	// 通知firstWaiter节点
	private void doSignal(Node first) {
		do {
			//如果通知节点转移至AQS队列失败,说明该节点状态有误,需要从CONDITION等待队列移除
			// 这个逻辑把firstwaiter节点从队列中移除
			if ( (firstWaiter = first.nextWaiter) == null)
				lastWaiter = null;
			first.nextWaiter = null;
		} while (!transferForSignal(first) &&           //如果transferForSignal方法成功将节点转移至AQS同步队列中,则停止循环
				 (first = firstWaiter) != null);
	}
	
	// 将Condition节点转为Signal节点,并从Condition队列转移至AQS的同步队列
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // 将consumer节点的waitStatus状态由 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);  // AQS重新入队,返回前一节点 即默认的 new Node()
        int ws = p.waitStatus; // waitStatus为初始值 0
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;  // 返回true
    }
	
	// 重新入队  自旋加入AQS队列的队尾tail
    private Node enq(final Node node) {
        for (;;) { // 自旋 
            Node t = tail;
            if (t == null) { // Must initialize   当tail为null,表示AQS队列为空,必须初始化 head、tail节点 
                if (compareAndSetHead(new Node())) //为head初始值 new Node()
                    tail = head; // tail赋初始值 new Date()
            } else {
                node.prev = t;  // 新加入的node(即为consumer)要在AQS队列中排队,node的prev即为原尾节点 
                if (compareAndSetTail(t, node)) { // CAS操作将tail由原来的t替换为现在的node
                    t.next = node; // 双向链表,原尾节点现在是倒数第二,t.next指向当前尾节点
                    return t; // 返回前一节点
                }
            }
        }
    }
	
  1. 之后Producer判断已满,调用await,仍是4步:a.将当前线程封装为Condition类型节点并存放于Condition队列、b.释放锁、c.从AQS队列中唤醒等待的线程、d.阻塞当前线程;
    a. 将producer封装为Condition类型的Node节点并放入Condition队列
    b. 释放producer线程占有的锁
    c. 从AQS队列中唤醒等待的线程,即 consumer线程
    d. 阻塞producer队列

  2. Consumer消费者唤醒,消费数据并signal通知Producer条件已满足,从Condition队列移至AQS的抢夺锁的同步队列中去
    consumer线程unpark唤醒时,从await的park处继续执行:


	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);  // consumer线程在AQS中unpark唤醒,从这里继续执行 
			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);
	}

consumer消费消息


    @Override
    public void run() {
        lock.lock();
        try {
            while (true) {
                i++;

                if (queue.size() == maxSize) {
                    // 队列已满,生产者阻塞,释放锁、唤醒其余竞争该锁的线程 即 消费者线程
                    System.out.println("队列已满,Producer 生产者阻塞并释放锁");
                    condition.await();
                }

                String msg = "Producer 生产消息 " + i;
                System.out.println(msg);
                queue.add(msg);
                // 通知消费者,但是此时锁未释放,所以其余线程并不能成功抢占锁;只有在await释放锁时消费者才能抢占
                condition.signal();
//                condition.signalAll();
                TimeUnit.SECONDS.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 除非出异常,否则不会调用这里的unlock来释放锁
            System.out.println("Producer 释放锁");
            lock.unlock();
        }

    }

-> Consumer发现队列为空,await阻塞当前线程并释放锁 -> AQS后续调度
-> 继续循环
...
...

总结:Conditon.await、Condition.signal
根据以上源码分析可得出:

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()内实现了4件事情:
a.将当前线程封装为Condition类型节点并存放于Condition队列、
b.释放锁、
c.从AQS队列中唤醒等待的线程、
d.阻塞当前线程;

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#signal内实现了1件事情:
从Condition队列中将firstWaiter 节点转至AQS同步队列中去,且节点Node类型由Condition转为默认状态0

至于await的线程什么时候被唤醒,要结合signal、lock.unlock()或者启停线程await()通过AQS同步队列来执行LockSupport.unpark唤醒;
当然,除了调用java层面的unpark,也可能是调用了线程的thread.interrupt()方法来中断线程触发的,interrupt()会更新线程的中断标识并且唤醒处于阻塞下的线程。
posted @ 2019-07-11 19:50  BigShen  阅读(202)  评论(0编辑  收藏  举报