同步锁和线程
Synchronization Lock,同步锁,或称监视锁,互斥锁。
它确保不同线程,安全访问共享资源。
一个线程,想执行以下代码时,需获取该对象的锁。该获取过程,由 JVM 自动控制:
public synchronized void method() {
// 方法体
}
// OR
synchronized(object) {
// do something
}
多个进程,想同时执行下面的代码,即尝试获取同一个对象的锁,叫线程竞争。
竞争会导致 Blocked 阻塞状态([[Java多线程的故事#线程的状态]])。线程 B 已经持有了锁,线程 A 想尝试获得,但失败。JVM 会将它置于锁池 Lock Pool 中,并改变 A 状态为阻塞。
Lock Pool 中存放着,所有试图获取锁,但没有如愿的线程。
线程 A 在池中,会等待锁的释放,一旦释放,便恢复运行。该过程称为等待机制。
与之相对,持有锁的线程,执行完代码以后会释放锁,JVM 会唤醒等待的线程。称之为唤醒机制。
例子
class Scratch {
public static void main(String[] args) {
SharedResource reource = new SharedResource();
Thread threadA = new Thread(() -> {
reource.doSomething("Thread A");
});
Thread threadB = new Thread(() -> {
reource.doSomething("Thread B");
});
threadA.start();
threadB.start();
}
}
class SharedResource {
public synchronized void doSomething(String threadName) {
System.out.println(threadName + " has entered the synchronized method");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " has exited the synchronized method");
}
}
输出:
Thread A has entered the synchronized method
Thread A has exited the synchronized method
Thread B has entered the synchronized method
Thread B has exited the synchronized method
关键字 volatile
synchronized
锁的是 object,定义是 is created using a class is said to be an instance of that class。
volatile
锁的是 Variables,更轻量。开销小,性能好。
直接的例子:
public class SynchronizedExample {
private boolean flag = false;
public synchronized void setFlag() {
flag = true;
}
public synchronized boolean isFlag() {
return flag;
}
}
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
flag = true;
}
public boolean isFlag() {
return flag;
}
}
特性对比:
属性 | volatile | synchronized |
---|---|---|
可见性 Visibility | yes | yes |
有序性 Ordering | yes | yes |
原子性 Atomicity | no | yes |
可见性 Visibility
- 定义:指当一个线程修改了共享变量的值,其他线程能立即看到。
- 原因:多核 CPU 和各自缓存之间出现的一致性问题。
- 例子:线程 A 修改了变量 x 的值,但线程 B 可能仍然看到的是 x 的旧值。
有序性 Ordering
- 定义:程序执行的顺序按照代码的先后顺序执行。
- 原因:为提高性能,编译器和 CPU 会对指令进行重排,导致执行与书写顺序不同。
- 例子:代码中,语句 1 在语句 2 之前,但实际执行时,颠倒顺序。
原子性 Atomicity
- 定义:指一个操作是不可中断。即使在多线程中,操作一旦开始,会让整个操作完成,而不被其他线程干扰。
- 问题:在多线程环境下,非原子操作可能会导致数据不一致。
- 例子:i++ 看似是一个操作,实际上包含了「读取 i」、「增加 1」、「写回内存」三个步骤,因此不是原子操作。
什么时候用 volatile?
适合用在一个线程写,多个线程读的场景,并且写入是简单赋值,不依赖于变量当前值。
适合的例子:
public class FlagHolder {
private volatile boolean flag = false;
public void setFlag() {
flag = true; // 简单赋值,不依赖当前值
}
public boolean isFlag() {
return flag;
}
}
不适合的例子:
public class Counter {
private volatile int count = 0;
public void increment() {
count++; // 不适合,因为这是复合操作,依赖当前值
}
}