tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础与面向对象

  • 高级进阶

  • 并发合集

    • Java中创建线程的几种方式
    • 并发相关概念与体系图
    • 线程状态与操作系统的用户态、内核态
    • 线程中的声明与守护线程_基础
    • 程序中的幽灵错误_基础
    • JDK并发包
    • 线程池相关
    • 并发中的安全集合
    • 生产者和消费者
    • 玩转单例模式
    • 一些工具类的原理
    • 并发包中的AQS
    • ThreadLocal与JMM
    • 锁的探究
    • 线程顺序执行与等待和通知
      • 等待wait()和通知notify()
        • 注意
      • 等待线程结束join()和谦让yield()
        • join-等待当前线程
        • 总结与本质
        • yield-谦让
        • 练习题
        • 解析
        • 更多
      • 锁的使用
        • 练习题-多个线程操作共享变量
        • 解析与追问
        • join的作用
        • 变量是否要加volatile
        • 哪个线程先执行
        • 为什么无法保证执行顺序?
        • 原子类的使用
        • 不同的锁lock和synchronized
        • 解析-不同的锁机制
        • 错误的发生
        • 使用Lock修正
        • 总结与关键事项
    • 线程间通信与等待、通知机制
    • 线程池与任务调度
  • JVM合集

  • 实战细节与其他

  • 代码之丑与提升

  • 《Java》学习笔记
  • 并发合集
EffectTang
2025-08-20
目录

线程顺序执行与等待和通知

# 线程顺序执行与等待和通知

# 等待wait()和通知notify()

为了支持多线程之间的协作,JDK提供了两个非常重要的接口线程等待wait()方法和通知notify()方法。这两个方法并不是在Thread类中的,而是输出Object类。这也意味着任何对象都可以调用这两个方法。

当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。这是什么意思呢?

比如,线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,而转为等待状态。等待到何时结束呢?线程A会一直等到其他线程调用了obj.notify()方法把它唤醒为止。这时,obj对象就俨然成为多个线程之间的有效通信手段。

如果一个线程调用了object.wait(),那么它就会进入object对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当object.notify()被调用时,它就会从这个等待队列中,随机选择一个线程,并将其唤醒

这里希望大家注意的是,这个选择是不公平的,并不是先等待的线程会优先被选择,这个选择完全是随机的。

如果某个线程进入了wait(),而没被唤醒,那它将会一直等待。

在进行程序编写的时候,一定注意最后一个线程是否能从wait中醒来,否则你的程序将一直处于运行中,无法停止。

除了notify()方法外,Object对象还有一个类似的notifyAll()方法,它和notify()的功能基本一致,但不同的是,它会唤醒在这个等待队列中所有等待的线程,而不是随机选择一个。”

if(number>0){
    this.wait();
}
System.out.println("this number");
this.notify();
1
2
3
4
5

一旦执行了 wait() 方法,当前线程会释放对象的锁并进入等待状态。在等待状态下,线程会暂停执行,不会继续执行 wait() 方法后面的代码,直到被其他线程唤醒。

以上是一个简单的例子。此时不会打印this number。

# 注意

Object.wait()方法并不是可以随便调用的。它必须包含在对应的synchronzied语句中,无论是wait()或者notify()都需要首先获得目标对象的一个监视器。而在他们执行后,也会释放这个监视器。

而Thread类的sleep方法可以在任何地方(sleep可以在任何地方睡觉)

wait不需要捕获异常,sleep需要捕获异常(可能发生超时等待)

  • Wait() -- 会释放锁
  • Sleep() -- 不会释放锁

Object.wait()和Thread.sleep()方法都可以让线程等待若干时间。除了wait()可以被唤醒外,另外一个主要区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。

怎么记忆呢?可以类比现实生活中,我们睡觉是不会让出床位的,只是无法做事而已

而wait对应——让出,排队的时候,你一旦让了,别人就可以先你一步去缴费了,因此它会让出锁

# 等待线程结束join()和谦让yield()

等待线程结束join() 和 谦让yield() 是两个用于线程交互和调度的方法,分别服务于不同的目的。

# join-等待当前线程

很多时候,一个线程的输入可能非常依赖于另外一个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。

多线程的本质其实就是多个线程,像队列排队一样在竞争cpu(多个cpu则有多个队列),而join,从名字看,似乎可以理解成插队(假如某个队列中)。

JDK提供了join()操作来实现这个功能,如下所示,显示了2个join()方法:

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
1
2

第一个join()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。

第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。

  • join():无限期等待,直到目标线程终结。
  • join(long millis):最多等待 millis 毫秒。
  • join(long millis, int nanos):等待更精确的时间。

比如:当在某个线程 A 上调用线程 B 的 join() 方法时,线程 A 将会阻塞(暂停执行),直到线程 B 完成其任务并终止。

Thread threadB = new Thread(() -> {
    // 执行线程B的任务
});

threadB.start();

try {
    threadB.join(); // 线程A在此阻塞,直到线程B执行完毕
} catch (InterruptedException e) {
    // 处理中断
}

// 这里只有在 threadB 完成后才会执行
System.out.println("Thread B has finished.");
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 总结与本质

核心作用

让当前执行线程等待(进入阻塞状态),直到被调用 join() 的那个线程(目标线程)执行完毕为止。

你可以把它理解为一种**线程间的“同步”或“顺序控制”**机制。例如,主线程启动了多个子线程进行数据加载,它必须等待所有子线程都加载完毕后,才能继续执行后续的汇总工作。

join()方法的本质是 wait():理解其基于 wait()/notify() 的实现,有助于明白它会释放目标线程对象的锁。

工作原理

join() 方法内部是基于 wait() 机制实现的。当线程A调用线程B的 join() 方法时,线程A会获取线程B对象的锁,然后调用 wait() 方法进入等待状态。当线程B执行结束时,JVM会自动调用 B.notifyAll(),从而唤醒正在等待线程B对象的所有线程(即所有调用了 B.join() 的线程)。

# yield-谦让

yield() 方法是一种线程礼让操作,它提示当前运行线程主动放弃 CPU 时间片,使调度器有机会将 CPU 执行权分配给其他同优先级的就绪线程。调用 yield() 不会阻止线程的执行,只是将其状态由运行态(Running)切换回可运行态(Runnable)。

但要注意,让出CPU并不表示当前线程不执行了。当前线程在让出CPU后,还会进行CPU资源的争夺,当前线程还可能会再次分配到然后继续执行,当然也可能不会,从而让其他线程先执行。

# 练习题

编写一个Java程序,使用两种不同的方式(Thread类和Runnable接口)创建并启动线程,分别输出“Hello from Thread 1”和“Hello from Thread 2”。要求线程顺序执行(先执行Thread 1,再执行Thread 2)。

// 参考答案
 Thread thread = new Thread(() -> {
            System.out.println("hello world1");
        });

        Runnable runnable = () -> {
            System.out.println("hello world-2");
        };

        Thread thread1 = new Thread(runnable);

        thread.start();
        thread.join();
        thread1.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 解析

代码能够保证顺序执行的关键在于 thread.join() 这一行:

  1. 主线程启动 thread
  2. 主线程调用 thread.join(),这意味着:
    • 主线程会阻塞(暂停执行)
    • 主线程将一直等待,直到 thread 线程完全执行结束
  3. 只有在 thread 执行完毕(打印完 "hello world1" 并终止)后,主线程才会从 join() 方法中返回,继续执行下一行代码
  4. 主线程随后启动 thread1,从而保证了 thread1 的打印操作总是在 thread 之后

如果没有join那行,则可能会出现

hello world1
hello world-2
# 或者
hello world-2
hello world1
1
2
3
4
5

# 更多

join方法可以写在某个run方法中吗?即某个线程执行时,使用

当然可以!这是一个非常实际且强大的用法。join() 方法完全可以写在某个线程的 run() 方法内部。

当一个线程(我们称之为 “当前线程”)在其执行过程中调用 otherThread.join() 时,它的行为是:“当前线程”会暂停自身执行,进入阻塞状态,等待 otherThread 线程完全执行完毕后,再继续执行自己后面的代码。

下面是一个典型的场景:一个“汇总线程”必须等待两个“数据加载线程”都完成后才能开始工作。

public class JoinInsideThread {

    public static void main(String[] args) {
        // 创建并启动数据加载线程A
        Thread dataLoaderA = new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟加载数据耗时
                System.out.println("数据A加载完毕!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 创建并启动数据加载线程B
        Thread dataLoaderB = new Thread(() -> {
            try {
                Thread.sleep(1500); // 模拟加载数据耗时,比A稍长
                System.out.println("数据B加载完毕!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        dataLoaderA.start();
        dataLoaderB.start();

        // 创建并启动汇总线程,它将等待A和B
        Thread summaryThread = new Thread(() -> {
            System.out.println("汇总线程开始,正在等待数据加载...");
            
            try {
                // !!!关键点:在summaryThread的run方法内调用join!!!
                dataLoaderA.join(); // 汇总线程在此阻塞,等待dataLoaderA结束
                System.out.println("汇总线程检测到DataLoaderA已完成。");
                
                dataLoaderB.join(); // 汇总线程继续阻塞,等待dataLoaderB结束
                System.out.println("汇总线程检测到DataLoaderB已完成。");
                
                // 只有A和B都完成后,才会执行下面的代码
                System.out.println("所有数据就绪,开始执行汇总计算...");
                // ... 这里是汇总计算的逻辑 ...
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        summaryThread.start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 锁的使用

# 练习题-多个线程操作共享变量

从一个练习题说起,编写一个多线程程序,模拟3个线程对共享变量counter进行10000次自增操作(counter++)。要求最终counter的值为30000。

很明显,这里涉及到并发,线程安全等问题。

public class Dema {

    int a = 0;

    public static void main(String[] args) throws InterruptedException {
        Dema dema = new Dema();

        ReentrantLock lock = new ReentrantLock();

        Runnable t1 = () -> {
            synchronized (dema){
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            }
        };

        Runnable t2 = () -> {
            synchronized (dema){
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            }
        };

        Runnable t3 = () -> {
						synchronized (dema){
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            }
            //lock.lock();
            //for (int i = 0; i < 10000; i++) {
            //    dema.a++;
            //}
            //lock.unlock();
        };

        Thread tt1 = new Thread(t1);

        Thread tt2 = new Thread(t2);

        Thread tt3 = new Thread(t3);

        tt1.start();
        tt2.start();
        tt3.start();


        tt1.join();
        tt2.join();
        tt3.join();

        System.out.println("最终 synchronized 结果为-- a:");
        System.out.println(dema.a);

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 解析与追问

# join的作用

如果去掉代码中的

tt1.join();
tt2.join();
tt3.join();
1
2
3

可能会发生 每次结果都不同,且结果始终远小于30000的情况,为什么呢?

即使去掉,join后,原代码也有锁呀,应该是线程安全的,为什么仍每次结果都不同。

原因:

结果不是30000的原因在于主线程没有等待其他线程执行完成就打印了结果。这是一个非常常见的多线程编程误区。

让我们仔细分析你的代码执行流程:

  1. 创建了三个线程并启动它们
  2. 立即执行 System.out.println("最终 synchronized 结果为-- a:"); 和 System.out.println(dema.a);
  3. 此时,三个线程很可能还在执行中,或者甚至还没开始执行

换句话说,你是在所有线程完成工作之前就打印了结果,所以看到的值是未完成累加的值(很可能是0)。

除了使用,join,还可以使用

  • CountDownLatch(更优雅)

欢迎大家自行查阅,以下是一个简单示例

  • countdownlatch
import java.util.concurrent.CountDownLatch;

public class Dema {
    volatile int a = 0;

    public static void main(String[] args) throws InterruptedException {
        Dema dema = new Dema();
        CountDownLatch latch = new CountDownLatch(3); // 创建计数器,初始值为3

        Runnable task = () -> {
            synchronized (dema){
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            }
            latch.countDown(); // 完成一个任务,计数器减1
        };

        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
        
        latch.await(); // 等待计数器变为0(即所有任务完成)
        
        System.out.println("最终 synchronized 结果为-- a:" + dema.a); // 应该是30000
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 变量是否要加volatile

上述代码中是否需要对变量,加上关键字-volatile,像这样 volatile int a = 0;,

volatile的作用:起到禁止指令重排(有序性)和可见性。

答案是:不用,原因如下

  1. synchronized 已经提供了足够的同步,保证了可见性和原子性
  2. volatile 在这里是多余的,因为 synchronized 块内的操作已经保证了:
    • 原子性:a++ 操作不会被中断
    • 可见性:一个线程对 a 的修改对其他线程立即可见

# 哪个线程先执行

就上述例子,哪个线程会先执行

 Thread tt1 = new Thread(t1);

  Thread tt2 = new Thread(t2);

  Thread tt3 = new Thread(t3);

  tt1.start();
  tt2.start();
  tt3.start();
1
2
3
4
5
6
7
8
9

tt1一定是先执行的吗?

答案:不,tt1 不一定先执行。 即使你先调用了 tt1.start(),也无法保证 tt1 的 run() 方法中的代码会先于 tt2 或 tt3 的代码执行。

# 为什么无法保证执行顺序?

当你调用 thread.start() 时,你并不是直接命令线程立刻运行。你只是向系统申请了一个新的执行流,并告诉系统这个线程已经就绪,可以被调度执行了。实际的执行顺序由操作系统的线程调度器(Thread Scheduler) 决定。

线程调度器决定执行顺序时,会考虑多种因素,以下几个关键因素决定了这种不确定性:

  1. 操作系统调度策略:不同的操作系统(Windows, Linux, macOS)有不同的线程调度算法。
  2. CPU 核心数:如果机器有多个 CPU 核心,多个线程可能同时在不同的核心上并行执行。在这种情况下,谈论“先”“后”几乎没有意义。
  3. 系统负载:操作系统中正在运行的其他进程和线程会竞争 CPU 资源,这会影响调度器的决策。
  4. 线程优先级:虽然你可以通过 setPriority() 方法给线程设置优先级(Thread.MAX_PRIORITY 等),但这只是一个提示(hint),而不是命令。调度器完全可以忽略它。依赖优先级来控制执行顺序是非常不可靠的。
  5. 时间片轮转:即使一个线程先开始运行,它也可能在执行完之前就被调度器中断(挂起),以便给其他线程运行的机会。

# 原子类的使用

上述的例子中,还有一种方式可以实现,而且不用加锁也可以实现

import java.util.concurrent.atomic.AtomicInteger;

public class DemaAtomic {

    // 使用 AtomicInteger 代替 volatile int
    AtomicInteger a = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        DemaAtomic dema = new DemaAtomic();
        
        Runnable t1 = () -> {
            for (int i = 0; i < 10000; i++) {
                dema.a.incrementAndGet(); // 原子性的增加操作
            }
        };

        Runnable t2 = () -> {
            for (int i = 0; i < 10000; i++) {
                dema.a.incrementAndGet();
            }
        };

        Runnable t3 = () -> {
            for (int i = 0; i < 10000; i++) {
                dema.a.incrementAndGet();
            }
        };

        Thread tt1 = new Thread(t1);
        Thread tt2 = new Thread(t2);
        Thread tt3 = new Thread(t3);

        tt1.start();
        tt2.start();
        tt3.start();

        tt1.join();
        tt2.join();
        tt3.join();
        
        System.out.println("最终 AtomicInteger 结果为 a:" + dema.a.get()); // 一定是30000
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

即使用原子类,AtomicInteger

使用 AtomicInteger 即使不加锁也可以实现最终结果为30000。这是因为 AtomicInteger 的 incrementAndGet() 方法是一个原子操作,它保证了在多线程环境下的线程安全性。

为什么它能保证原子性呢?AtomicInteger 的实现基于 CAS (Compare-And-Swap) 操作,这是一种无锁的同步机制。

而,CAS 操作是硬件级别的原子操作,通常由 CPU 直接支持。

但,它只适用于线程竞争不太激烈的场景。

特性 AtomicInteger (CAS) synchronized (锁)
机制 乐观锁,无阻塞 悲观锁,阻塞
性能 高(无上下文切换) 较低(有上下文切换)
适用场景 低竞争到中等竞争 高竞争场景
是否会导致死锁 不会 可能
编程复杂度 低 中等

# 不同的锁lock和synchronized

在上述的例子中,多个线程共享一个变量,对齐进行增加,如果同时使用lock和synchronized,结果是否正确呢(为 30000)?

public class Dema {

    int a = 0;

    public static void main(String[] args) throws InterruptedException {
        Dema dema = new Dema();

        ReentrantLock lock = new ReentrantLock();

        Runnable t1 = () -> {
            synchronized (dema){
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            }
        };

        Runnable t2 = () -> {
            synchronized (dema){
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            }
        };

        Runnable t3 = () -> {
            //synchronized (dema){
            //    for (int i = 0; i < 10000; i++) {
            //        dema.a++;
            //    }
            //}
            lock.lock();
            for (int i = 0; i < 10000; i++) {
                dema.a++;
            }
            lock.unlock();
        };

        Thread tt1 = new Thread(t1);

        Thread tt2 = new Thread(t2);

        Thread tt3 = new Thread(t3);

        tt1.start();
        tt2.start();
        tt3.start();


        tt1.join();
        tt2.join();
        tt3.join();

        System.out.println("最终 synchronized 结果为-- a:");
        System.out.println(dema.a);

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 解析-不同的锁机制

程序运行后,它的最终结果可能不是30000的原因在于:你混合使用了两种不同的同步机制(synchronized 和 ReentrantLock),但它们保护的是同一个共享资源,却没有使用相同的锁对象。

让我们仔细分析你的代码:

  1. 线程 t1 和 t2 使用 synchronized(dema) 进行同步
  2. 线程 t3 使用 ReentrantLock 进行同步

问题是:这两种同步机制使用不同的锁对象:

  • synchronized(dema) 使用 dema 对象作为锁
  • lock.lock() 使用 lock 对象(ReentrantLock实例)作为锁

由于锁对象不同,这些同步机制之间无法提供互斥访问。这意味着:

  • 线程 t1 和 t2 之间可以互斥(因为它们使用相同的锁对象 dema)
  • 线程 t3 只与自己互斥(因为它使用不同的锁对象 lock)
  • 但线程 t3 与线程 t1/t2 之间没有互斥,它们可以同时访问和修改 a

# 错误的发生

由于 t3 与 t1/t2 之间没有同步,它们可能同时执行 a++ 操作,导致之前提到的竞争条件:

  1. t1 读取 a 的值(比如100)
  2. t3 也读取 a 的值(也是100)
  3. t1 计算 100+1=101 并写入
  4. t3 计算 100+1=101 并写入
  5. 结果:两次增加操作只实现了一次有效增加

上述的错误是因为,锁的不同,或者说同步机制不同;

扩展:

synchronized(xx.class) 使用的是类对象作为锁(每个类在JVM中都有一个唯一的Class对象)。这与实例锁(synchronized(this) 或 synchronized(实例对象))完全不同。

最后是一些关于ReentrantLock的注意事项:

  • 不能直接用 Lock 锁住一个任意对象(如 dema)
  • Lock 实例自己就是锁对象,而不是用来锁其他对象
  • 可以通过使用统一的 Lock 实例来实现相同的线程同步效果
  • 在大多数情况下,为需要同步的资源创建一个专用的 Lock 实例是最佳实践

# 使用Lock修正

public class Dema {
    int a = 0;
    private final ReentrantLock lock = new ReentrantLock(); // 统一的锁实例

    public static void main(String[] args) throws InterruptedException {
        Dema dema = new Dema();

        Runnable t1 = () -> {
            dema.lock.lock(); // 所有线程使用同一个锁实例
            try {
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            } finally {
                dema.lock.unlock();
            }
        };

        Runnable t2 = () -> {
            dema.lock.lock(); // 使用同一个锁实例
            try {
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            } finally {
                dema.lock.unlock();
            }
        };

        Runnable t3 = () -> {
            dema.lock.lock(); // 使用同一个锁实例
            try {
                for (int i = 0; i < 10000; i++) {
                    dema.a++;
                }
            } finally {
                dema.lock.unlock();
            }
        };

        // 创建和启动线程...
        // 最终结果一定是30000
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 总结与关键事项

所以要想实现数据在并发的情况下正确,需要做到以下三点:

  1. 一致性原则:保护同一共享资源的所有线程必须使用相同的同步机制和锁对象。
  2. 可见性原则:确保对一个变量的修改对其他线程立即可见(volatile 或同步机制可以保证)。
  3. 原子性原则:确保复合操作(如 a++)作为一个不可分割的单元执行。
上次更新: 2025/08/21, 15:38:29
锁的探究
线程间通信与等待、通知机制

← 锁的探究 线程间通信与等待、通知机制→

最近更新
01
Spring中Bean的生命周期
09-03
02
数据不丢失与准确类
09-01
03
线程池与任务调度
08-31
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式