Java并发系列之四 Condition源码解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Java并发系列之四 Condition源码解析

1. Condition的应用



在上一篇文章我们用Condition实现了线程交替打印0和1功能。调用Condition的await(),能实现将当前线程释放获取到的相应的锁。并且阻塞当前线程,直到其他线程调用了同一Condition的signal(),如果有多个线程在同一个Condition上调用了await()方法,那么这些线程将会被封装成一个Node节点,加入到Condition内部维护的单链表的尾部。调用了Condition的signal()。将会把Condition内部的单链表表首的Node节点,放入到Condition所在锁的AQS中竞争锁。


接下来我将使用ReentrantLock和Condition来模拟下学生等待下课铃响出去玩的情景

package com.peter.tips.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Created by jiangbin on 2018/6/4.
 */
public class StudentAndBell {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Thread student = new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    lock.lock();
                    System.out.println("我等下课铃响,出去耍~");
                    condition.await();//释放锁,等待下课铃声响起
                    System.out.println("出去耍~");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread bell = new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    lock.lock();
                    System.out.println("下课铃响了....");
                    condition.signal();//通知学生下课了
                }  finally {
                    lock.unlock();
                }
            }
        };
        student.start();
        bell.start();
        try {
            student.join();
            bell.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main");
    }
}

输出结果如下:

我等下课铃响,出去耍~

下课铃响了….

出去耍~

main

2. Condition实现原理



首先我们来看下Condition的定义


java.util.concurrent.locks.Condition.java
public interface Condition {
    /**
     * 阻塞当前线程,直到该线程通过signal重新唤醒
     * {@linkplain Thread#interrupt interrupted}.
     *
     /
    void await() throws InterruptedException;
    /**
     * await过程可以被中断
     */
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    /**
     * 唤醒一个在该Condition上等待的线程.
     *
     */
    void signal();
    /**
     * 唤醒所有在该Condition上等待的线程.
     *
     */
    void signalAll();
}

Condition接口的具体实现类为ConditionObject。它是AbstractQueuedSynchronizer非静态内部类。

public class ConditionObject implements Condition, java.io.Serializable {
        /** 指向单向链表的头部结点 */
        private transient Node firstWaiter;
        /** 指向单向链表的尾部结点 */
        private transient Node lastWaiter;
        ...
}
我们再看下Node的定义
static final class Node{
    ...省略其他定义
    //prev 是在AQS双向链表中指向 前一个结点
    volatile Node prev;
    //next 是在AQS双向链表中指向 后一个结点
    volatile Node next;
    //阻塞的线程
    volatile Thread thread;
    //nextWaiter 是在Condition单向列表指向后一个节点
    Node nextWaiter;
}

看了上面关于ConditionObject和Node的定义,我们大概可以猜到,Condition内部维护的单向链表,当有线程调用了Condition的await(),就会将该线程封装成Node结点,并放入到单向链表尾部。当有线程调用了Condition的signal(),便会将Condition中的链表头部的节点,放入到AQS的双向链表中通过自旋(即不断循环)获取锁。signalAll()会将Condition链表中所有结点都放到AQS中自旋

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//加入到Condition的单向链表中
            int savedState = fullyRelease(node);//释放当前线程获取到的锁
            int interruptMode = 0;
            //只要不在AQS队列中就一直阻塞,当调用了signal该node就会加入到队列中
            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);
        }
 public final void signal() {
            if (!isHeldExclusively())//如果没获取到锁是会报错的
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)//拿到等待队列的第一个Node,放入AQS队列
                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) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //结点加入到AQS队列,
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);//把所有的节点都放到AQS列表中
        }


相关文章
|
1天前
|
存储 Java 计算机视觉
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
24 15
|
1天前
|
算法 搜索推荐 Java
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
23 6
|
1天前
|
存储 算法 搜索推荐
【潜意识Java】期末考试可能考的高质量大题及答案解析
Java 期末考试大题整理:设计一个学生信息管理系统,涵盖面向对象编程、集合类、文件操作、异常处理和多线程等知识点。系统功能包括添加、查询、删除、显示所有学生信息、按成绩排序及文件存储。通过本题,考生可以巩固 Java 基础知识并掌握综合应用技能。代码解析详细,适合复习备考。
10 4
|
1天前
|
存储 Java
【潜意识Java】期末考试可能考的选择题(附带答案解析)
本文整理了 Java 期末考试中常见的选择题,涵盖数据类型、控制结构、面向对象编程、集合框架、异常处理、方法、流程控制和字符串等知识点。每道题目附有详细解析,帮助考生巩固基础,加深理解。通过这些练习,考生可以更好地准备考试,掌握 Java 的核心概念和语法。
|
1天前
|
Java 编译器 程序员
【潜意识Java】期末考试可能考的简答题及答案解析
为了帮助同学们更好地准备 Java 期末考试,本文列举了一些常见的简答题,并附上详细的答案解析。内容包括类与对象的区别、多态的实现、异常处理、接口与抽象类的区别以及垃圾回收机制。通过这些题目,同学们可以深入理解 Java 的核心概念,从而在考试中更加得心应手。每道题都配有代码示例和详细解释,帮助大家巩固知识点。希望这些内容能助力大家顺利通过考试!
|
Java API
java多线程 -- Condition 控制线程通信
Api文档如此定义: Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
834 0
|
15天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
71 17
|
26天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
11天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
28天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。

热门文章

最新文章

推荐镜像

更多