Java程序使用synchronized
关键字对一个对象进行加锁:
synchronized
保证了代码块在任意时刻最多只有一个线程能执行。
用法如下:
public class Main {
public static void main(String[] args) throws Exception {
var add = new AddThread();
var dec = new DecThread();
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Count.count);
}
}
class Count {
// 定义一个锁
public static final Object lock = new Object();
// 两个线程的共享变量
public static int count = 0;
}
class AddThread extends Thread {
public void run() {
for (int i=0; i<10000; i++) {
synchronized(Count.lock) {
Count.count += 1;
}
}
}
}
class DecThread extends Thread {
public void run() {
for (int i=0; i<10000; i++) {
synchronized(Count.lock) {
Count.count -= 1;
}
}
}
}
它表示用Count.lock
实例作为锁,两个线程在执行各自的synchronized(Count.lock) { ... }
代码块时,必须先获得锁,才能进入代码块进行。
执行结束后,在synchronized
语句块结束会自动释放锁。这样一来,对Count.count
变量进行读写就不可能同时进行。上述代码无论运行多少次,最终结果都是0。
synchronized(Count.lock) { // 获取锁
...
} // 释放锁
使用synchronized
解决了多线程同步访问共享变量的正确性问题。但是,它的缺点是带来了性能下降。因为synchronized
代码块无法并发执行。此外,加锁和解锁需要消耗一定的时间,所以,synchronized
会降低程序的执行效率。
如何使用synchronized
:
- 找出修改共享变量的线程代码块;
- 选择一个共享实例作为锁;
- 使用
synchronized(lockObject) { ... }
。
在使用synchronized
的时候,不必担心抛出异常。因为无论是否有异常,都会在synchronized
结束处正确释放锁。
总结:
- 多线程同时读写共享变量时,会造成逻辑错误,因此需要通过
synchronized
同步; - 同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
- 注意加锁对象必须是同一个实例;
- 对JVM定义的单个原子操作不需要同步。