1. 线程同步
当多个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。
所以当多个线程任务读写同一变量时,会产生数据不一致的情况。
为了解决这个问题,就需要在线程中对该变量进行加锁与解锁来保证数据的一致性。
Java程序使用synchronized
关键字对一个对象进行加锁:
synchronized(lock) {
n = n + 1;
}
注意必须是对同一个对象进行加锁,此外,Java中规定了如下几个原子操作:
- 基本类型的赋值,例如
int a = b;
- 引用类型赋值。
如果方法中只有一句原子语句,则不需要加锁。
2. 同步方法
在多线程任务中,有时需要锁住的对象非常多,让线程自己选择要锁住的对象容易使得代码逻辑混乱,通常的方法是将synchronized
逻辑封装起来。
例如,编写一个计数器如下:
public class Counter {
private int count = 0;
public void add(int n) {
synchronized(this) {
count += n;
}
}
public void dec(int n) {
synchronized(this) {
count -= n;
}
}
public int get() { // 因为赋值时原子指令,所以不需要同步
return count;
}
}
这样,线程在调用add()
,dec()
方法时,不必关心内部的同步逻辑就可以正常使用,我们称这样的类为线程安全的类。
注意,synchronized
锁住的是this
对象,所以在创建多个Counter
实例时,它们之间互不影响。同时,synchronized
可直接对方法进行修饰,与上述代码功能一致。
public class Counter {
private int count = 0;
public synchronized void add(int n) {
count += n;
}
public synchronized void dec(int n) {
count -= n;
}
public int get() { // 因为赋值时原子指令,所以不需要同步
return count;
}
}
3. 可重入锁
一个线程对对象A
进行加锁后,其他线程需等待该线程释放对象A
的锁后才能访问对象A
。
那么是否允许同一个线程对某一个对象加锁多次呢?例子如下:
public class Counter {
private int count = 0;
public synchronized void add_abs(int n) { // 相加后然后取绝对值
add(n);
return count >= 0 ? count : -count;
}
public synchronized void add(int n) {
count += n;
}
public synchronized void dec(int n) {
count -= n;
}
}
实际上,如果调用add_abs()
方法,加锁和释放锁的顺序如下:
- 对
Counter
实例进行第一次加锁,记录+1。 - 调用
add()
方法时,对Counter
实例进行第二次加锁,记录+1。 add()
方法结束,记录-1。add_abs()
方法结束,记录-1,此时记录为0,释放该锁。
所以,JVM允许一个线程重复获取同一个锁,这种锁被称为可重复锁。
4. 死锁
不同线程获取多个不同对象的锁时,可能造成死锁,如下:
public void add(int m) {
synchronized(lockA) { // 获得lockA的锁
this.value += m;
synchronized(lockB) { // 获得lockB的锁
this.another += m;
} // 释放lockB的锁
} // 释放lockA的锁
}
public void dec(int m) {
synchronized(lockB) { // 获得lockB的锁
this.another -= m;
synchronized(lockA) { // 获得lockA的锁
this.value -= m;
} // 释放lockA的锁
} // 释放lockB的锁
}
死锁发生后就只能手动结束JVM进程才可以恢复。
那么如何避免死锁呢?应使线程获取锁的顺序要一致,修改上述代码如下:
public void add(int m) {
synchronized(lockA) { // 获得lockA的锁
this.value += m;
synchronized(lockB) { // 获得lockB的锁
this.another += m;
} // 释放lockB的锁
} // 释放lockA的锁
}
public void dec(int m) {
synchronized(lockA) { // 获得lockA的锁
this.another -= m;
synchronized(lockB) { // 获得lockB的锁
this.value -= m;
} // 释放lockA的锁
} // 释放lockB的锁
}