同步资源
同步资源,是对资源(类、方法、代码块、变量)进行同步控制。在java中的多线程操作(修改)同一个共享变量的时候,如果不进行同步控制,将会导致数据不准确,相互之间产生冲突。因此加入同步机制,来避免一个线程没有操作完资源之前,被另外几个线程调用,产生多种不一致的结果。
JVM需要对被线程共享的数据(保存在堆中的实例变量、保存在方法区的类变量)进行协调,不需要协调栈当中的数据(栈当中的数据被线程所私有)。常用的java多线程同步解决方案有:volatile、synchronized、ThreadLocal、ReentrantLock。
Synchronized
synchronized实现同步资源,有两种用法:修饰方法和修饰代码块;
修饰方法,每个java对象有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法,在调用方法前,需要获取内置锁,如果获取不到,线程就处于阻塞状态;当用synchronized修饰static方法时,锁住的是整个类;代码如下:
@Slf4j
@Data
@AllArgsConstructor
public class SynchronizedMethodBank implements Bank {
private Double money;
@Override
public synchronized void saveMoney(double money) {
System.err.println(format("save money %s", money));
this.money += money;
}
@Override
public synchronized void withdrawMoney(double money) {
System.err.println(format("withdraw money %s", money));
this.money -= money;
}
@Override
public double queryMoney() {
System.err.println(format("Current money is %s", this.money));
return this.money;
}
}
修饰代码块,被synchronized修饰的代码块会自动被加上内置锁。同步是一个资源开销大的动作,因此尽量缩小同步内容,通常做法是,如果没有必要同步整个方法,则使用synchronized代码块同步关键代码即可。
@Slf4j
@Data
@AllArgsConstructor
public class SynchronizedBlockBank implements Bank {
private Double money;
@Override
public void saveMoney(double money) {
System.err.println(format("save money %s", money));
synchronized (this){
this.money += money;
}
}
@Override
public synchronized void withdrawMoney(double money) {
System.err.println(format("withdraw money %s", money));
synchronized (this) {
this.money -= money;
}
}
@Override
public double queryMoney() {
System.err.println(format("Current money is %s", this.money));
return this.money;
}
}
Volatile
volatile比较有意思,当使用volatile修饰资源时,相当于告诉JVM该资源可能会被其他线程更新。因此每个线程在使用该资源当时候,都需要重新计算,而不是使用寄存器中的值(根据内存中的值再刷新下当前线程寄存器中的值,刷新完后再使用)。也就是常说的volatile保证了可见性,但是不保证原子性。
public class VolatileBank implements Bank {
private volatile Double money;
@Override
public void saveMoney(double money) {
synchronized (this){
this.money +=money;
}
}
@Override
public void withdrawMoney(double money) {
synchronized (this){
this.money -= money;
}
}
@Override
public double queryMoney() {
return this.money;
}
}
ReentrantLock
ReentrantLock与使用synchronized方法具有相同的语义,再其基础上扩展了其能力;使用ReentrantLock要在finally中释放锁,否则会出现死锁;
@Data
@AllArgsConstructor
public class ReentrantLockBank implements Bank {
private Double money;
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void saveMoney(double money) {
reentrantLock.lock();
try{
this.money += money;
}finally {
reentrantLock.unlock();
}
}
@Override
public void withdrawMoney(double money) {
reentrantLock.lock();
try{
this.money -= money;
}finally {
reentrantLock.unlock();
}
}
@Override
public double queryMoney() {
return this.money;
}
}
ThreadLocal
使用ThreadLocal管理变量,虽然有static修饰,但每个使用该变量的线程都获得该变量的副本,且副本之间相互独立;这样每个线程可以随意修改自己的变量副本,而不会对其他线程产生影响。
@Data
@AllArgsConstructor
public class ThreadLocalBank implements Bank {
private static ThreadLocal<Double> doubleThreadLocal = new ThreadLocal<>();
@Override
public void saveMoney(double money) {
doubleThreadLocal.set(doubleThreadLocal.get() + money);
}
@Override
public void withdrawMoney(double money) {
doubleThreadLocal.set(doubleThreadLocal.get() - money);
}
@Override
public double queryMoney() {
return doubleThreadLocal.get();
}
}