【高薪程序员必看】万字长文拆解Java并发编程!(3-2):并发共享问题的解决与分析

简介: wait方法和notify方法都是Object类的方法:让当前获取锁的线程进入waiting状态,并进入waitlist队列:让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒:在waitlist队列中挑一个线程唤醒:唤醒所有在waitlist队列中的线程它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常park方法和unpark方法是LockSupport类中的方法。

目录

3.5. wait-notify机制

3.5.1. wait-notify介绍

3.5.2. 原理

3.5.3. wait和sleep的区别

3.5.4. join原理

3.6. park-unpark机制

3.6.1. park-unpark介绍

3.6.2. park-unpark与wait-notify的区别

3.6.3. park-unpark原理

3.7. 线程状态转换


3.5. wait-notify机制

3.5.1. wait-notify介绍

wait方法和notify方法都是Object类的方法

  • object.wait():让当前获取锁的线程进入waiting状态,并进入waitlist队列
  • object.wait(long n):让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒
  • object.notify():在waitlist队列中挑一个线程唤醒
  • object.notifyAll():唤醒所有在waitlist队列中的线程

它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常

3.5.2. 原理

wait调用条件:owner线程获取了该对象的锁,但是发现自己条件不满足使用共享资源的条件/竞态条件存在时,会调用wait方法进入waiting状态并进入Monitor对象的waitlist中,释放对象锁

处于waiting状态中的线程可以被notify/notifyAll方法唤醒,唤醒之后不一定马上可以获取锁,仍需进入EntryList竞争锁

3.5.3. wait和sleep的区别

  • 从属层面:sleep是Thread方法,wait是Object方法
  • 使用层面:sleep不需要强制和synchronized使用,wait方法需要和synchronized使用
  • 作用效果:sleep在睡眠时不会释放对象锁,wait在等到时会释放对象锁

3.5.4. join原理

join是使用了保护性暂停模式,并在此基础上扩展了超时等待

  • 使用经历时间和这一轮应该等待的时间来确保等待时间不超过指定的时间
  • 当条件不满足但是又经历一次notify,还是进入while循环,这时等待的时间可能会超过指定时间
public final synchronized void join(long millis)throws InterruptedException {
    //开始时间
    long base = System.currentTimeMillis();
    //经历时间
    long now = 0;
    
    if (millis < 0) {
        //如果指定等待时间等于0则抛出异常
        throw new IllegalArgumentException("timeout value is negative");
    }
  
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        //如果指定等待时间为正数
        while (isAlive()) {
            //求出这一轮循环应该等待的时间
            long delay = millis - now;
            if (delay <= 0) {
                //等待时间小于0直接退出循环
                break;
            }
            //等待这一轮应该等待的时间,防止唤醒后的条件不满足再次等待原有时间导致超时
            wait(delay);
            //求出经历的时间
            now = System.currentTimeMillis() - base;
        }
    }
}

image.gif

3.6. park-unpark机制

3.6.1. park-unpark介绍

park方法和unpark方法是LockSupport类中的方法

  • LockSupport.park():用于暂停当前的线程
  • LockSupport.unpark(thread):用于恢复某个线程,既可以在park()之前调用也可以在之后调用

3.6.2. park-unpark与wait-notify的区别

  • wait-notify是Object类的方法,必须结合加锁对象使用,park-unpark没有限制
  • notify随机唤醒一个线程,notifyAll唤醒全部线程,park(thread)唤醒指定的线程
  • notify必须用在wait之后,unpark既可以用在park之前,也可以用在park之后

3.6.3. park-unpark原理

每个线程都会关联一个Parker(jvm层面)对象,有一个当前线程许可计数permit

  • LockSupport.park():调用park时,会先检查permit,如果大于0则将permit-1后返回,线程继续执行.如果小于0则线程进入阻塞状态,等待被唤醒或打断
  • LockSupport.unpark(thread):调用unpark时,将permit+1,

3.7. 线程状态转换

image.gif 编辑

以下是线程各个状态之间的转换以及调用的方法

NEW-->RUNNABLE

  • thread.start():thread线程的状态NEW-->RUNNABLE

RUNNABLE<-->WAITING

  • 线程进入synchronized(obj)代码块,获取对象锁之后
  • obj.wait():当前线程的状态RUNNABLE-->WAITING
  • obj.notify() 或 obj.notifyAll() 或 obj.interrupt():
  • 竞争锁成功,当前线程的状态WAITING-->RUNNABLE
  • 竞争锁失败,当前线程的状态WAITING-->BLOCKED
  • thread.join():当前线程中调用线程thread的join方法会在thread线程对象上的监视器等待,当前线程的状态RUNNABLE-->WAITING
  • thread线程运行结束 或 被打断:当前线程的状态WAITING-->RUNNABLE
  • LockSupport.park(): 当前线程的状态RUNNABLE-->WAITING
  • LockSupport.unpark(thread):指定线程的状态WAITING-->RUNNABLE

RUNNABLE<-->TIMED_WAITING

  • 线程进入synchronized(obj)代码块,获取对象锁之后
  • obj.wait(long n):当前线程的状态RUNNABLE-->TIMED_WAITING
  • 当前线程等待n秒 或 被唤醒和打断:
  • 竞争锁成功,当前线程的状态TIMED_WAITING-->RUNNABLE
  • 竞争锁失败,当前线程的状态TIMED_WAITING-->BLOCKED
  • Thread.sleep(long n):当前线程的状态RUNNABLE-->TIMED_WAITING
  • 当前线程等待n秒:竞争锁成功,当前线程的状态TIMED_WAITING-->RUNNABLE
  • LockSupport.parkNanos(long nanos) 或 LockSupport.parkUnitl(long millis):当前线程状态:RUNNABLE-->TIMED_WAITING
  • LockSupport.unpark(thread) 或 被打断 或 等待超时:目标线程状态TIMED_WAITING-->RUNNABLE

RUNNABLE<-->BLOCKED

  • thread线程使用synchronized(obj)获取对象锁竞争失败,thread线程状态RUNNABLE-->BLOCKED
  • 持有锁的线程的同步代码块执行完毕,唤醒阻塞中的线程竞争锁
  • 竞争成功BLOCKED-->RUNNABLE
  • 竞争失败BLOCKED

RUNNABLE<-->TERMINATED

  • 当前线程所有代码运行完毕RUNNABLE-->TERMINATED
相关文章
|
5月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
249 6
|
5月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
341 1
|
6月前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
1145 2
|
5月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
249 0
|
6月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
548 100
|
5月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
174 4
|
5月前
|
缓存 安全 Java
如何理解Java中的并发?
Java并发指多任务交替执行,提升资源利用率与响应速度。通过线程实现,涉及线程安全、可见性、原子性等问题,需用synchronized、volatile、线程池及并发工具类解决,是高并发系统开发的关键基础。(238字)
328 5
|
9月前
|
Java 数据库连接 API
2025 更新必看:Java 编程基础入门级超级完整版指南
本教程为2025更新版Java编程基础入门指南,涵盖开发环境搭建(SDKMAN!管理JDK、VS Code配置)、Java 17+新特性(文本块、Switch表达式增强、Record类)、面向对象编程(接口默认方法、抽象类与模板方法)、集合框架深度应用(Stream API高级操作、并发集合)、模式匹配与密封类等。还包括学生成绩管理系统实战项目,涉及Maven构建、Lombok简化代码、JDBC数据库操作及JavaFX界面开发。同时提供JUnit测试、日志框架使用技巧及进阶学习资源推荐,助你掌握Java核心技术并迈向高级开发。
887 5
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
175 1