JAVA 并发 - CONDITION

4 篇文章 0 订阅
4 篇文章 0 订阅

java 并发 – condition

什么是condition?

       Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。

       Java里 sychronized和Lock+Condtion 都属于管程模型,Condition 在管程模型中代表的就是等待的条件。Condition在Lock的基础上使用,在原来Lock的基础上实现了可以基于多种条件来让线程实现同步的效果增加了多个条件后,我们可以更有针对性,也更灵活的协调多种条件下的线程协调。

       条件(也称为条件队列或 条件变量)为一个线程暂停执行(“等待”)直到另一线程通知某些状态条件现在为真提供了一种方法。由于对该共享状态信息的访问发生在不同的线程中,因此必须对其进行保护,因此某种形式的锁与该条件相关联。等待条件提供的关键属性是,它像原子一样自动释放关联的锁并挂起当前线程Object.wait。

       一个Condition实例本质上绑定到锁。要获取Condition特定Lock 实例的实例,请使用其newCondition()方法。

       一个Condition实现可以提供与Object监视方法不同的行为和语义,例如保证通知的顺序,或者在执行通知时不需要保持锁定。如果实现提供了这种特殊的语义,则实现必须记录这些语义。

注意:
       Condition实例只是普通对象,它们本身可以用作synchronized语句中的目标,并且可以调用自己的监视器wait和 notification方法。获取Condition实例的监视器锁或使用其监视器方法与获取Lock与该实例的监视器锁或使用Condition其 等待和信令方法没有特定的关系 。建议避免混淆Condition ,除非可能在实例自己的实现中,否则不要以这种方式使用实例。

       条件等待的三种形式(可中断,不可中断和定时)在某些平台上的实现容易程度和性能特征可能会有所不同。特别是,可能很难提供这些功能并维护特定的语义,例如排序保证。此外,中断线程的实际挂起的能力可能并不总是在所有平台上都可行。

       因此,不需要为所有三种等待形式定义完全相同的保证或语义的实现,也不需要支持中断线程的实际挂起。

       需要一个实现来清楚地记录每个等待方法提供的语义和保证,并且当实现确实支持中断线程挂起时,则它必须服从此接口中定义的中断语义。

       由于中断通常意味着取消,并且通常不经常进行中断检查,因此与正常方法返回相比,实现可能更喜欢对中断做出响应。即使可以证明中断发生在另一个可能解除线程阻塞的操作之后,也是如此。实现应记录此行为。

       Condition是个接口,基本的方法就是await()和signal()方法;

       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能做什么?

       - 可重入锁(ReentrantLock)是 synchronized 关键字的扩展,更加灵活。还有一种ReentrantLock应用场景是和Condition搭配使用,实现多线程环境下等待状态条件的功能。Object.wait 和 Object.notify 是和 synchronized 配合使用的,条件变量Condition.await 和 Condition.signal 是和ReentrantLock相关联的。

       - JDK并发包中的 ArrayBlockingQueue 使用了Condition来同步队列的空/满状态。

       - Condition实现生产者、消费者模式

Condition 源码解析

在这里插入图片描述

/**
 * @since 1.5
 */
public interface Condition {
	
	/**
	 * 使当前线程等待,直到发出信号或被中断为止 
	 */
    void await() throws InterruptedException;
    
    /**
	 * 使当前线程等待,直到发出信号为止。
	 */
    void awaitUninterruptibly();

	/**
	 * 使当前线程等待,直到发出信号或被中断或经过指定的等待时间为止
     */
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    
    /**
     * 使当前线程等待,直到发出信号或被中断或经过指定的等待时间为止。
     */
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    
    /**
     * 使当前线程等待,直到发出信号或被中断或指定的截止时间过去为止。
     */
    boolean awaitUntil(Date deadline) throws InterruptedException;
    /**
     * 唤醒一个等待线程
     */
    void signal();

	/**
	* 唤醒所有等待的线程。
    */
    void signalAll();
}

在这里插入图片描述

public final void await() throws InterruptedException {
    // 1.如果当前线程被中断,则抛出中断异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2.将节点加入到Condition队列中去,这里如果lastWaiter是cancel状态,那么会把它踢出Condition队列。
    Node node = addConditionWaiter();
    // 3.调用tryRelease,释放当前线程的锁
    long savedState = fullyRelease(node);
    int interruptMode = 0;
    // 4.为什么会有在AQS的等待队列的判断?
    // 解答:signal操作会将Node从Condition队列中拿出并且放入到等待队列中去,在不在AQS等待队列就看signal是否执行了
    // 如果不在AQS等待队列中,就park当前线程,如果在,就退出循环,这个时候如果被中断,那么就退出循环
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 5.这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了4中的while循环
    // 自旋等待尝试再次获取锁,调用acquireQueued方法
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

// 新增一个条件节点
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;
}

public final void signal() {
    // 先判断当前线程是否获取了锁,否则异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

// 摘除头节点,把头节点移到同步队列
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
     // CAS 将状态设置为0,0是一个无效的状态
    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);
    int ws = p.waitStatus;
    
    // 唤醒线程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

// 这个方法是AQS中的,node进入同步队列
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
// 唤醒等待了最久的线程
// 其实就是,将这个线程对应的 node 从条件队列转移到阻塞队列
public final void signal() {
    // 调用 signal 方法的线程必须持有当前的独占锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
// 从条件队列队头往后遍历,找出第一个需要转移的 node
private void doSignal(Node first) {
    do {
        // 将 firstWaiter 指向 first 节点后面的第一个,因为 first 节点马上要离开了
        // 如果将 first 移除后,后面没有节点在等待了,那么需要将 lastWaiter 置为 null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        // 因为 first 马上要被移到阻塞队列了,和条件队列的链接关系在这里断掉
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
      // 这里 while 循环,如果 first 转移不成功,那么选择 first 后面的第一个节点进行转移,依此类推
}
// 将节点从条件队列转移到阻塞队列
// true 代表成功转移
// false 代表在 signal 之前,节点已经取消了
final boolean transferForSignal(Node node) {
    
    // CAS 如果失败,说明此 node 的 waitStatus 已不是 Node.CONDITION,说明节点已经取消,
    // 既然已经取消,也就不需要转移了,方法返回,转移后面一个节点
    // 否则,将 waitStatus 置为 0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
  
    // enq(node): 自旋进入阻塞队列的队尾
    // 注意,这里的返回值 p 是 node 在阻塞队列的前驱节点
    Node p = enq(node);
    int ws = p.waitStatus;
    // ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁,直接唤醒 node 对应的线程。唤醒之后会怎么样,后面再解释
    // 如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用,上篇介绍的时候说过,节点入队后,需要把前驱节点的状态设为 Node.SIGNAL(-1)
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 如果前驱节点取消或者 CAS 失败,会进到这里唤醒线程,之后的操作看下一节
        LockSupport.unpark(node.thread);
    return true;
}
Condition实战

场景:基于多个线程顺序打印0-50的数据

public class Test {

	public static void main(String[] args) {

		Data data = new Data();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				data.printA();
			}
		}, "A").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				data.printB();
			}
		}, "B").start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				data.printC();
			}
		}, "C").start();

	}

}

class Data{
	Lock lock = new ReentrantLock();

	Condition condition1 = lock.newCondition();
	Condition condition2 = lock.newCondition();
	Condition condition3 = lock.newCondition();
	private int num = 1;
	private int size = 0;
	public void printA() {
		lock.lock();
		try {
			while (num != 1) {
				condition1.await();
			}
			System.out.println(Thread.currentThread().getName() + ":" + size ++);
			num = 2;
			condition2.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void printB() {
		lock.lock();
		try {
			while (num != 2) {
				condition2.await();
			}
			System.out.println(Thread.currentThread().getName() + ":" + size ++);
			num = 3;
			condition3.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	public void printC() {
		lock.lock();
		try {
			while (num != 3) {
				condition3.await();
			}
			System.out.println(Thread.currentThread().getName() + ":" + size ++);
			num = 1;
			condition1.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

执行结果:

A:1
B:2
C:3
A:4
B:5
C:6
A:7
B:8
C:9
A:10
B:11
C:12
A:13
B:14
C:15
A:16
B:17
C:18
A:19
B:20
C:21
A:22
B:23
C:24
A:25
B:26
C:27
A:28
B:29
C:30

参考文章:

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html
https://javadoop.com/post/AbstractQueuedSynchronizer-2

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值