【高薪程序员必看】万字长文拆解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 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
192 4
|
6月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
378 1
|
6月前
|
缓存 安全 Java
如何理解Java中的并发?
Java并发指多任务交替执行,提升资源利用率与响应速度。通过线程实现,涉及线程安全、可见性、原子性等问题,需用synchronized、volatile、线程池及并发工具类解决,是高并发系统开发的关键基础。(238字)
370 5
|
6月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
273 6
|
6月前
|
安全 前端开发 Java
从反射到方法句柄:深入探索Java动态编程的终极解决方案
从反射到方法句柄,Java 动态编程不断演进。方法句柄以强类型、低开销、易优化的特性,解决反射性能差、类型弱、安全性低等问题,结合 `invokedynamic` 成为支撑 Lambda 与动态语言的终极方案。
277 0
|
存储 缓存 Java
Java 中的伪共享详解及解决方案
Java 中的伪共享详解及解决方案
402 0
Java 中的伪共享详解及解决方案
|
6月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
332 1
|
6月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
332 1
|
7月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
318 0