#假设两个线程并发读写同一个整型变量,初始值为零,每个线程加 50 次,结果可能是什么?
import java.util.concurrent.atomic.AtomicInteger;
import java.lang.Thread;
public class AddInteger{
private AtomicInteger count = new AtomicInteger(0);
public int addInt()throws InterruptedException{
Thread thread1 = new Thread(()->{
for(int i=0;i<50;i++){
count.addAndGet(1);
}
});
Thread thread2 = new Thread(()->{
for(int i=0;i<50;i++){
count.addAndGet(1);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
return count.get();
}
}如何实现三个线程并发执行后,这个第四个线程才执行
使用这个concurrent.CountDownLatch; 这个首先定义这个 CountDownLatch; CountDownLatch latch = new CountDownLatch(3); for(int i=0;i<3;i++){ thread(()->{latch.countDown()}) } thread(()->{latch.wait()})
Java 内存模型(JMM)是什么?
JMM(Java Memory Model)是一套规范,规定了多线程读写共享变量时必须遵守的规则。它要解决的就是并发编程的三个核心问题:可见性、原子性、有序性。
注意 JMM 不是 JVM 的内存结构(堆、栈、方法区那些),两者经常被混淆。
主内存与工作内存
JMM 抽象出两类内存:
- 主内存:所有线程共享,存放共享变量。
- 工作内存:每个线程私有,对应 CPU 缓存、寄存器等,保存共享变量的副本。
线程不能直接操作主内存,必须先把变量读到自己的工作内存,修改后再写回主内存。问题就出在“什么时候读、什么时候写回”。
1. 可见性
假设线程 T1 修改了标志位 running = false,但只改了自己缓存里的副本,还没写回主内存;线程 T2 读到的仍是旧值 true,于是循环停不下来。
private volatile boolean running = true; // 去掉 volatile,T2 可能永远看不到修改volatile 保证写操作立即刷新到主内存,并让其他线程缓存中的副本失效,下次读取必须从主内存重新加载。
2. 原子性
count++ 看似一步,实际分为读取、加一、写回三步。两个线程同时执行时,可能都读到 5,各自加一后都写回 6,少加了一次。
解决办法是加锁(synchronized、ReentrantLock),或者使用 AtomicInteger 这类基于 CAS 的原子类。注意 volatile 不能保证原子性。
3. 有序性
编译器和 CPU 会在不影响单线程结果的前提下重排指令。典型例子是双重检查单例:
instance = new Singleton();
// 正常顺序:分配内存 → 初始化对象 → 赋值引用
// 可能重排为:分配内存 → 赋值引用 → 初始化对象如果发生重排,另一个线程可能拿到一个“还没初始化完”的对象。给 instance 加上 volatile,通过内存屏障禁止这种重排。
happens-before 原则
JMM 用 happens-before 规则描述操作之间的可见性保证,常见的有:
- 同一线程内,前面的操作 happens-before 后面的操作。
- 解锁 happens-before 之后对同一把锁的加锁。
- 对 volatile 变量的写 happens-before 之后对它的读。
Thread.start()happens-before 该线程内的所有操作。
满足这些规则,前一个操作的结果就一定对后一个操作可见。
总结
JMM 通过主内存和工作内存的抽象,配合 volatile、synchronized、final 以及 happens-before 规则,控制变量的读写时机并限制指令重排,从而保证多线程下的可见性、原子性和有序性。
📮 联系方式
微信:BuZhiCoder | QQ:1776418445 扫码添加,备注「求职」优先通过
