• 并发编程六、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()会更新线程的中断标识并且唤醒处于阻塞下的线程。
    
  • 相关阅读:
    C++奇数阶幻方(主动生成)
    一次C++作业 C++的I/O流类库 3 (学生注册信息登记程序)
    一次C++作业 C++的I/O流类库2 [文本文件和二进制文件]
    一次C++作业(模板类的构造& C++的I/O流类库)1
    一次C++作业 try-throw-catch
    关于英语作文AI批改的思考(含定向高分方案)
    PhoneNumber类
    一次C++作业
    服务器较全面入手介绍(持续更新)
    JS中多个onload冲突解决办法
  • 原文地址:https://www.cnblogs.com/Qkxh320/p/thread_06.html
Copyright © 2020-2023  润新知