【Java并发基础1】多线程核心知识详解(synchronized、volatile、线程间通信)
本文系统梳理 Java 多线程中的核心知识,重点包括:synchronized 锁机制、volatile 内存可见性、线程通信(wait/notify)、线程安全问题。
一 、线程安全问题来源
产生线程安全问题的根本原因:
- 共享数据
- 执行顺序不可控,可能发生指令重排
- 非原子操作
解决方案:
- synchronized
- Lock
- 原子类
二、synchronized 关键字(核心锁机制)
互斥性:同一时刻同一把锁只有一个线程能进入同步代码块
由JVM 层实现:会有一些优化,虽然用了synchronized 关键字,但是会是一个自适应锁的过程。下面是几种优化:
自适应流程:无锁 →[某线程进入] →偏向锁→[几个线程竞争] → 轻量级锁→[竞争激烈] → 重量级锁(后面会讲到这几种锁状态,状态只能升级,不能降级)
锁粗化过程:加解锁耗资源,会把多个连续的小锁,合并成一个大锁,
锁消除:如果JVM 发现锁“根本不可能被竞争”,直接把锁去掉(就是无锁)
自动加锁/解锁:进入synchronized 修饰的代码块 → 遇到 ‘{’ 加锁------执行代码块 → 遇到 ’}‘ 解锁
可重入性:同一线程可以多次获取同一把锁,不会死锁,但是只有最外部是真正锁
原理:
·记住持有锁的线程【owner = 当前线程
·每加一次锁计数器+1,解锁计数器-1【count = 重入次数
用法:
①修饰代码块(更灵活 )---只锁关键代码,性能更好
修饰代码块要自己定义一个锁对象,不同线程如果使用的是“同一个锁对象”,才会互斥。即只有一个线程持有锁,这个线程解锁后,别的线程才能用这个锁。
class Counter {
private int count = 0;
public void increment() {
synchronized (this) { //this 表示锁当前对象,如obj.increment()锁的就是obj
count++;
}
}
}
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) { //锁指定对象lock,避免外部代码误用 this 作为锁,常用
count++;
}
}
}
②修饰普通方法(锁当前对象this)---同一时间只有一个线程能执行这个方法,不同对象不会互斥
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
③修饰静态方法(锁类)---锁的是:Counter.class,所有实例共享同一把
class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
}
三、Java中线程不安全/安全的类
| 常见线程不安全类 | 线程安全类 |
| ArrayList | Vector(不推荐) |
| LinkedList | Hashtable(不推荐) |
| HashMap | ConcurrentHashMap(推荐) |
| TreeMap | StringBuffer(使用synchronized) |
| HashSet |
有不推荐因为所有方法都加锁 → 粗粒度锁 → 性能差 |
| TreeSet | |
| StringBuilder |
四、volatile 关键字(保证内存可见性)
解决的问题:可见性 + 有序性(部分)不解决:原子性
· 可见性问题:线程读取变量的流程【主内存 → 工作内存 → CPU使用】
线程A修改变量 → 写到自己缓存 → 没及时刷新到主内存
线程B读取变量 → 读的是旧值 ×· 有序性问题:JVM 和 CPU 会做优化,也就是指令重排序
a = 1;flag = true;执行顺序可能变为flag = true 先执行,a = 1 后执行(被重排)
· 原子性:volatile int count = 0;
count++; // × 线程不安全作用:保证从主内存读取变量,保证读到新值√
volatile boolean flag = true;
new Thread(() -> {
while (flag) {}
System.out.println("结束");
}).start();
new Thread(() -> {
flag = false;
});
| 对比 | synchronized | volatile |
| 原子性 | √ | × |
| 可见性 | √ | √ |
| 有序性 | √ | √(部分) |
| 性能 | 较低 | 较高 |
五、线程通信:wait / notify
为什么需要线程通信:线程是抢占式执行,执行顺序不可控。
核心方法(Object类):
- wait() //释放锁,线程进入等待状态,进入等待队列
【唤醒条件:①notify / notifyAll②超时③interrupt()】
- notify() //随机唤醒一个等待线程,不会立即释放锁,当前线程执行完才释放锁
- notifyAll() //唤醒所有等待线程,但其他等待的线程仍然需要竞争锁
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("等...");
lock.wait();
System.out.println("被叫醒了");
} catch (InterruptedException e) {}
}
}).start(); //开启线程
new Thread(() -> {
synchronized (lock) {
System.out.println("叫一下");
lock.notify();
}
}).start();
| 对比 | wait | sleep |
|---|---|---|
| 所属类 | Object | Thread |
| 是否释放锁 | √ | 错 |
| 使用位置 | synchronized内 | 任意 |
| 作用 | 线程间通信 | 线程暂停 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)