【高薪程序员必看】万字长文拆解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
相关文章
|
6月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
273 6
|
6月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
378 1
|
7月前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
1205 2
|
6月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
276 0
|
7月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
563 100
|
6月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
188 4
|
6月前
|
缓存 安全 Java
如何理解Java中的并发?
Java并发指多任务交替执行,提升资源利用率与响应速度。通过线程实现,涉及线程安全、可见性、原子性等问题,需用synchronized、volatile、线程池及并发工具类解决,是高并发系统开发的关键基础。(238字)
370 5
|
6月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
333 2
|
6月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
302 1
|
7月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
497 16
下一篇
开通oss服务