深入学习JAVA并发编程(上)

进程和线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows 中进程是不活动的,只是作为线程的容器

二者对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

并行和并发

并发

单核 cpu 下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的线程使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。总结为一句话就是:微观串行

一般将这种轮流使用CPU的做法称作并发,concurrent.

并行

多个任务在同一时刻真正同时运行 ,必须依靠多核 CPU、多处理器硬件实现。 每个 CPU 核心独立执行一个线程,同一时间点有多条指令在不同物理核心上执行,是物理层面的同时执行。

异步和同步

同步调用是调用函数 / 接口后,调用线程卡住,原地阻塞等待,必须等被调用逻辑全部执行完毕、返回结果,当前线程才能继续往下执行。

异步调用指的是发起调用后,调用线程不会阻塞,立刻继续执行后续代码;被调用任务在后台独立运行。 等后台任务完成,通过回调函数、消息、通知等方式把结果返还给调用方。

创建和运行线程

1.使用Thread类创建线程

java 复制代码
package com.example.demo;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "test1")
public class Test {
    public static void main(String[] args) {
        Thread thread1 = new Thread() {
            public void run() {
                log.info("running...");
            }
        };

        thread1.start();
        log.info("running...");
    }
}

2.Runnable配合Thread创建线程

java 复制代码
package com.example.demo;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "test1")
public class Test {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.info("running...");
            }
        };
        Thread thread = new Thread(runnable, "t1");

        thread.start();

        log.info("running");
    }
}



//也可以使用lambda简化
@Slf4j(topic = "test1")
public class Test {
    public static void main(String[] args) {
        Runnable runnable = () -> {log.info("running...");}
        };
        Thread thread = new Thread(runnable, "t1");

        thread.start();

        log.info("running");
    }
}

3.使用Callable创建线程

java 复制代码
package com.example.demo;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

@Slf4j(topic = "test1")
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.info("running...");
                Thread.sleep(1000);
                return 100;
            }
        });
        Thread thread = new Thread(integerFutureTask);
        thread.start();
        Integer i = integerFutureTask.get();
        System.out.println("返回的结果为"+i);
    }
}

常见的方法

1.start和run

start() 真正启动线程

run() 普通成员方法,不会开新线程

  • 重写 run () 是写业务,start () 才是启动入口 我们重写 run () 写任务逻辑,但绝对不能手动调用 run;必须用 start 让操作系统调度执行。
  • 为什么不能多次 start? 线程生命周期:新建→就绪→运行→终止。线程终止后生命周期结束,无法二次唤醒。
  • 实现 Runnable 接口的场景 无论继承 Thread 还是实现 Runnable,规则完全一致:start()开线程,run()只是普通方法。
java 复制代码
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("执行线程:" + Thread.currentThread().getName());
        });

        System.out.println("主线程名:" + Thread.currentThread().getName());

        // 方式1 start:新开线程
        thread.start();
        // 方式2 run:主线程执行
        // thread.run();
    }
}

2.sleep与yield

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 状态,然后调度执行其它同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果
  2. 具体的实现依赖于操作系统的任务调度器

3.join

join() 属于线程实例方法,A 线程调用 B.join () :A 线程阻塞等待,直到 B 线程执行完毕,A 才继续向下运行。 本质:利用wait实现等待,底层依赖等待通知机制。

  • 阻塞对象 主线程 / 调用线程阻塞,被 join 的子线程正常跑。 例:main 调用t.join() → main 卡住,t 正常执行;t 跑完,main 放行。

  • 可被 interrupt 中断 等待期间如果调用线程被interrupt()打断,join 抛出InterruptedException

  • 限时版本 join(2000):最多等 2 秒。2 秒内子线程结束则提前放行;2 秒不管子线程跑没跑完,调用线程恢复执行。

  • 底层原理 join 带synchronized锁,锁是子线程对象; 内部循环判断线程是否存活,存活就wait(超时时间);子线程执行完退出时 JVM 会自动执行notifyAll唤醒等待线程。

  • 会释放锁吗? join 内部调用wait()等待阶段会释放锁;sleep/yield 不会释放锁。

java 复制代码
public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("子线程执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();

        System.out.println("主线程开始等待子线程");
        t.join(); // main阻塞,等t执行完
        System.out.println("子线程结束,主线程继续运行");
    }
}

4.interrupt

Java不会强制终止线程interrupt() 只是给线程打一个中断标记位 (设为 true),相当于发通知:"有人希望你停止运行"。 最终要不要退出、怎么退出,由线程自身代码主动判断中断标识自行决定,属于协作式中断 。 旧方法stop()强制杀死线程,会导致锁、数据错乱,已被废弃。

  1. 线程正在阻塞(sleep /wait/join) 线程卡在sleep()Object.wait()join()阻塞等待时,调用interrupt()
  • 立刻唤醒线程
  • 抛出 InterruptedException
  • 自动把中断标志重置为 false(清空标记)

2.线程正常运行(RUNNABLE) 仅把中断标记改为 true,不会抛异常、不会停线程,代码正常往下跑,需要线程手动判断标记自行结束。

java 复制代码
Thread t = new Thread(()->{
    while(true){
        // 运行中,自己判断中断标记
        if(Thread.currentThread().isInterrupted()){
            System.out.println("收到中断,退出");
            break;
        }
    }
});
t.start();
t.interrupt(); // 打标记

普通线程和守护线程

  1. 普通线程(用户线程 / User Thread,默认所有线程都是这类)

JVM 退出规则:只要任意一条普通用户线程还在运行,JVM 就不会关闭。 主线程、手动 new Thread 创建的线程默认都是用户线程。 主线程结束后,如果还有用户子线程在跑,程序不会终止。

  1. 守护线程(Daemon Thread,后台线程)

作用:为普通用户线程提供后台服务,比如垃圾回收 GC、定时任务、心跳检测。 JVM 退出规则:所有普通用户线程全部执行完毕,无论守护线程是否还在运行,JVM 直接强制退出

java 复制代码
Thread t = new Thread(...);
t.setDaemon(true); // 设为守护线程
t.start();

线程的生命周期

Java 线程一共 6 种固定状态,任何时刻线程只处于其中一种状态,状态之间只能按规则流转。

java 复制代码
// JDK源码枚举全部6种状态
public enum State {
    NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
}
  1. NEW 新建状态
  • 执行 new Thread() 创建线程对象,还没有调用 start ()
  • 仅创建 Java 对象,操作系统内核线程未创建,没有分配栈。
  • 只能调用start()进入下一阶段;调用多次 start 直接报错。
  1. RUNNABLE 可运行

  2. 就绪 Ready:线程准备好了,等待 CPU 调度分配时间片;

  3. 运行 Running:抢到 CPU 时间片,正在执行 run () 代码。

触发进入 RUNNABLE:

  • 调用start()
  • sleep 时间结束、wait 被唤醒、锁竞争成功;
  • yield 让出 CPU 后回到 RUNNABLE。
  1. BLOCKED 阻塞(synchronized 锁阻塞)

只有一种场景进入:线程去抢 synchronized 同步锁失败,锁被别的线程占用,线程卡在锁池等待。

  • 只和synchronized有关;Lock 锁不会进入 BLOCKED(Lock 用 AQS,属于 WAITING)。
  • 持有锁的线程释放锁后,该线程重新回到 RUNNABLE 抢锁。
  1. WAITING 无限等待

线程无限期等待,必须被手动唤醒,不会自己恢复。 进入方式(3 种):

  1. Object.wait() 无参;
  2. Thread.join() 无参;
  3. Lock 的lockSupport.park()

唤醒条件:

  • wait:其他线程执行notify()/notifyAll()
  • join:被等待的线程执行完毕;
  • park:unpark 唤醒。 唤醒后回到 RUNNABLE。
  1. TIMED_WAITING 限时等待

有固定等待时长,时间到自动唤醒;也可提前被唤醒。 常用方法:

  1. Thread.sleep(毫秒)
  2. Object.wait(long) 带超时
  3. Thread.join(long) 带超时
  4. LockSupport.parkNanos / parkUntil

sleep、join 限时版本都属于这个状态。

  1. TERMINATED 终止 / 死亡

线程 run 方法正常执行完毕,或者异常跑完,生命周期彻底结束。

  • 终止后的线程不能再次 start (),报非法状态异常;
  • 无法复活。

Synchronized

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

1.阻塞式的解决方案:synchronized,Lock

2.非阻塞式的解决方案:原子变量

核心语法:

java 复制代码
synchronized(object)
{
    临界区
}

原理:

synchronized实际是使用了对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

方法上的synchronized:

成员方法:

java 复制代码
class Test{
    public synchronized void test() {

    }
}
//等价于
class Test{
    public void test() {
        synchronized(this) {

        }
    }
}

静态方法:

java 复制代码
class Test{
    public synchronized static void test() {

    }
}
//等价于
class Test{
    public static void test() {
        synchronized(Test.class) {

        }
    }
}

变量的线程安全

局部变量线程安全分析

java 复制代码
public static void test1(){
    int i = 10;
    i++;
}

每个线程调用test1()时创建局部变量,会在每个线程的栈帧内存中被创建多份,存入局部变量表中,因此不存在共享,也就是线程安全的

java 复制代码
class ThreadSafe {
    public void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
        list.add("1");
    }

    public void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

//线程不安全
class ThreadSafeSubClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

常见的线程安全类

线程安全类指的是多个线程调用他们同一个实例的某个方法时,是线程安全的

1.String

2.Integer

String,Integer等都是不可变类,所以是线程安全的

3.StringBuffer

4.Random

5.Vector

6.Hashtable

7.JUC包下的类

Synchronized底层

Java对象头

以32位虚拟机为例:

Monitor(监视器|管程)

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

Monitor结构如下:

part和unpart的使用

LockSupport.park():阻塞当前线程,休眠停下

LockSupport.unpark(thread):唤醒指定被阻塞的线程

java 复制代码
import java.util.concurrent.locks.LockSupport;

public class ParkDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("线程开始运行,准备阻塞");
            // 当前线程停下、阻塞
            LockSupport.park();
            System.out.println("线程被唤醒,继续执行");
        });
        t1.start();

        // 主线程休眠2秒之后唤醒t1线程
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LockSupport.unpark(t1);
    }
}

与 Object 的 wait & notify 相比

  • wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 unpark 不必
  • park & unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notify

底层原理

每个线程都有自己的一个 Parker 对象,由三部分组成 _counter, _cond 和 _mutex 打个比喻

  • 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
  • 调用 park 就是要看需不需要停下来歇息
    • 如果备用干粮耗尽,那么钻进帐篷歇息
    • 如果备用干粮充足,那么不需停留,继续前进
  • 调用 unpark,就好比令干粮充足
    • 如果这时线程还在帐篷,就唤醒让他继续前进
    • 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
    • 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

重新理解线程状态转换

NEW(新建):线程对象创建,还没有调用start()

RUNNABLE(可运行):细分就绪 (等待 CPU 调度)、正在 CPU 运行

BLOCKED(阻塞):等待 synchronized 监视器锁

WAITING(无限等待):无时限休眠,需要手动唤醒

TIMED‑WAITING(限时等待):带超时时间的休眠

TERMINATED(终止):线程执行完毕

1、编号 1:NEW → RUNNABLE

调用 thread.start(),线程进入就绪 (RUNNABLE) 状态,等待操作系统分配 CPU 时间片。

2、编号 2:RUNNABLE → WAITING

触发方法:

  1. obj.wait()(synchronized 锁内)
  2. thread.join()
  3. LockSupport.park() 线程进入无限等待,需要别的线程唤醒。

3、编号 3:WAITING → RUNNABLE

唤醒条件:

  • notify() / notifyAll() 唤醒 wait 线程
  • join 的目标线程执行完毕
  • LockSupport.unpark(线程) 唤醒之后线程回到可运行状态

4、编号 4:WAITING ←→ TIMED‑WAITING

RUNNABLE → TIMED‑WAITING(编号 5)

限时休眠进入超时等待: sleep(long)wait(time)join(time)LockSupport.parkNanos()

TIMED‑WAITING → RUNNABLE(编号 6)

  1. 等待时间超时自动苏醒
  2. 中途被其他线程提前唤醒

5、编号 7:RUNNABLE → TIMED‑WAITING

调用sleep(毫秒),线程进入限时等待

6、编号 8:TIMED‑WAITING → RUNNABLE

睡眠时间结束,线程恢复就绪状态

7、编号 9:RUNNABLE ↔ BLOCKED

  1. RUNNABLE → BLOCKED :线程去争抢synchronized对象锁失败,进入锁阻塞队列
  2. BLOCKED → RUNNABLE:锁被释放,该线程抢到监视器锁,变为就绪状态

8、编号 10:RUNNABLE → TERMINATED

线程 run () 方法代码执行完毕、或者异常结束,线程进入终止状态,生命周期结束。

线程的活跃性

死锁

多个线程互相持有对方需要的锁,双方都不肯释放自己手上的锁,互相僵持、永久阻塞,这就是死锁。

线程 A:拿着锁 1,等待获取锁 2 线程 B:拿着锁 2,等待获取锁 1 两个线程卡住,谁都无法往下执行。

产生死锁的 4 个必要条件

  • 互斥条件:同一时间,锁只允许一个线程占用(synchronized、ReentrantLock 都是排他锁)
  • 请求与保持:线程已经持有一把锁,现在又去申请另一把新锁,旧锁不会释放
  • 不可剥夺:别人手上的锁,不能被强制抢走,只能持有者主动释放
  • 循环等待:线程之间形成环路等待:A 等 B、B 等 C、C 等 A
java 复制代码
public class DeadLockDemo {
    public static void main(String[] args) {
        Object lockA = new Object();
        Object lockB = new Object();

        //线程1:先拿A,再请求B
        new Thread(() -> {
            synchronized (lockA){
                System.out.println("线程1拿到锁A");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (lockB){
                    System.out.println("线程1拿到锁B");
                }
            }
        }).start();

        //线程2:先拿B,再请求A
        new Thread(() -> {
            synchronized (lockB){
                System.out.println("线程2拿到锁B");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
                synchronized (lockA){
                    System.out.println("线程2拿到锁A");
                }
            }
        }).start();
    }
}

怎么定位死锁

1.根据jps查看java线程数据

2.在根据jstack查看线程内的具体情况

活锁

活锁是两个线程互相改变了对方的结束条件,导致谁也无法结束线程

java 复制代码
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            // 期望减到 0 退出循环
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超过 20 退出循环
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

饥饿

多个线程争抢锁,部分线程长期拿不到锁、一直处于等待,永远得不到执行机会,线程被 "饿死"。

死锁:线程互相卡住,全都不动;

活锁:线程反复尝试、反复失败;

饥饿:有的线程一直抢不到资源

ReentrantLock

属于 java.util.concurrent.locks可重入独占锁,JDK 层面实现;

对比 synchronized:synchronized 是虚拟机底层监视器锁。

ReentrantLock具备以下条件:

1.可中断

2.可以设置超时时间

3.可以设置为公平锁

4.支持多个条件变量

与sychronized一样,都支持可重入

可充入指的是同一个线程如果首次获得了这把锁,那么因为他是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获取这把所的时候,自己也会被锁住

java 复制代码
reenreantlock.lock();
try{
   //临界锁
}finally{
   //释放锁
   reentrantLock.unlock();
}

锁超时

java 复制代码
@Slf4j
public class Demo {
    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();

        new Thread(()->{
            try{
                if(!reentrantLock.tryLock(5, TimeUnit.SECONDS)){
                    log.info("t1获取不到锁");
                    return;
                }
                
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
                log.info("t1获取到锁了");
        }).start();

        new Thread(()->{
            try{
                if(reentrantLock.tryLock(5, TimeUnit.SECONDS)){
                    log.info("t2获取到锁了");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }
}

公平锁

线程按照请求锁的先后顺序排队获取锁,遵守 FIFO,禁止线程插队,不会出现线程饥饿。

synchronized 监视器锁永远是非公平锁,不支持排队策略,无法设置为公平。

ReentrantLock默认是不公平的。我们可以指定参数将其变成公平锁。

java 复制代码
// true = 公平锁;不传参数/false = 非公平锁
ReentrantLock lock = new ReentrantLock(true);

非公平锁对比

非公平:新来线程可以直接尝试抢占锁,插队成功就跳过排队线程

优点:吞吐量高、速度快

缺点:部分老线程长期抢不到锁,发生线程饥饿

条件变量

Condition 就是锁配套的等待‑通知工具,基于 ReentrantLock 创建,用来代替 synchronized 的 wait()‑notify()。

await()执行后会释放锁,进入conditionObject等待,被唤醒时重新竞争lock锁

java 复制代码
@Slf4j
public class Demo2 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        
        new Thread(()->{
            try{
                if(!lock.tryLock(5, TimeUnit.SECONDS)){
                    condition1.await();
                }
                log.info("t1获取锁成功");
                condition1.signalAll();
            }catch(Exception e){
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try{
                if(!lock.tryLock(5, TimeUnit.SECONDS)){
                    condition1.await();
                }
                log.info("t2获取锁成功");
                condition1.signalAll();
            }catch(Exception e){
                e.printStackTrace();
            }
        }).start();
    }
}

固定线程运行顺序

先打印2,在打印1

1。notify&wait

java 复制代码
@Slf4j
public class Demo3 {
    //同步锁对象
    static Object object =  new Object();
    public static void main(String[] args) {
        AtomicBoolean flag = new AtomicBoolean(false);
        new Thread(()->{
            synchronized (object){
                try{
                    while(!flag.get()){
                        log.info("t1获取锁失败");
                        object.wait();
                    }
                    log.info("t1获取锁成功");
                    object.notifyAll();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"t1").start();

        new Thread(()->{
            synchronized (object){
                log.info("t2获取锁成功");
                flag.set(true);
                object.notifyAll();
            }
        },"t2").start();
    }
}

2.part&unpart

java 复制代码
@Slf4j
public class Demo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
                LockSupport.park();
                log.info("t1执行了");
        },"t1");

        Thread t2 = new Thread(()->{
                log.info("t2执行了");
                LockSupport.unpark(t1);
        },"t2");

        t1.start();
        t2.start();
    }
}

交替输出的三种实现

1.nodify&wait

java 复制代码
@Slf4j
public class Demo4 {
    static Object object = new Object();
    static boolean t1 = true,t2 = false,t3 = false;
    static int cnt = 15;
    public static void main(String[] args) {
        new Thread(()->{
            for(int i=0;i<5;i++) {
                synchronized (object) {
                    try {
                        while (!t1) {
                            object.wait();
                        }
                        log.info("a");
                        t1 = false;
                        t2 = true;
                        object.notifyAll();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

        new Thread(()->{
            for(int i=0;i<5;i++) {
                synchronized (object) {
                    try {
                        while (!t2) {
                            object.wait();
                        }
                        log.info("b");
                        t2 = false;
                        t3 = true;
                        object.notifyAll();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

        new Thread(()->{
            for(int i=0;i<5;i++){
                synchronized (object){
                    try {
                        while(!t3){
                            object.wait();
                        }
                        log.info("c");
                        t3 = false;
                        t1 = true;
                        object.notifyAll();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();

    }
}

2.await&signal

java 复制代码
@Slf4j
public class Demo4 {
    // 创建可重入锁
    private static final ReentrantLock lock = new ReentrantLock();
    // 三个独立的条件队列,分别管控 a、b、c线程
    private static final Condition condA = lock.newCondition();
    private static final Condition condB = lock.newCondition();
    private static final Condition condC = lock.newCondition();

    // 执行标记
    private static int flag = 1; //1打印a,2打印b,3打印c

    public static void main(String[] args) {
        // 线程A 打印a
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                try {
                    while (flag != 1) {
                        // 不该当前线程执行,进入A条件队列等待
                        condA.await();
                    }
                    log.info("a");
                    flag = 2;
                    // 唤醒b线程
                    condB.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "线程A").start();

        // 线程B 打印b
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                try {
                    while (flag != 2) {
                        condB.await();
                    }
                    log.info("b");
                    flag = 3;
                    condC.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "线程B").start();

        // 线程C 打印c
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                try {
                    while (flag != 3) {
                        condC.await();
                    }
                    log.info("c");
                    flag = 1;
                    condA.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }, "线程C").start();
    }
}

3.part&unpart

java 复制代码
@Slf4j
public class Demo4 {
    public static void main(String[] args) {
        Thread tA = null, tB = null, tC = null;

        tA = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                log.info("a");
                LockSupport.unpark(tB);
            }
        }, "A");

        tB = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                log.info("b");
                LockSupport.unpark(tC);
            }
        }, "B");

        tC = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                LockSupport.park();
                log.info("c");
                LockSupport.unpark(tA);
            }
        }, "C");

        tA.start();
        tB.start();
        tC.start();

        // 主线程最先唤醒A,开启循环
        LockSupport.unpark(tA);
    }
}
相关推荐
zzzll11112 小时前
HashMap、HashTable、ConcurrentHashMap 详细区别与深度解析
开发语言·python
PC2005-cloud2 小时前
KubeSphere学习笔记:部署nginx
笔记·学习·nginx
Bernice橘子2 小时前
职场英语学习计划Day033
学习
老师我太想进步了20262 小时前
C语言版讲解函数递归
c语言·开发语言
XS0301062 小时前
Spring AI:两种内置 Advisor 快速实现 RAG
java·人工智能·spring
mifengxing2 小时前
LeetCode 238 除自身以外数组的乘积|无除法O(n)时间+O(1)空间双解法
java·算法·leetcode
猿长大人2 小时前
C# | 函数式编程入门
开发语言·c#·.net
W_326003 小时前
Python 组合数据类型——序列(列表 & 元组)
开发语言·python
Billy121383 小时前
Day10-C++20 Ranges(上):惰性求值与组合式数据处理
开发语言·c++进阶学习