一、不可变对象设计介绍
线程安全问题主要是由于多个线程对共享资源同时进行操作产生的问题。针对该问题通常使用加锁的方式锁定共享资源,使得同一时刻只能有一个线程对共享资源进行操作。
不过加锁,难免会造成性能上的损耗。针对该问题,存在无锁的解决方案:不可变对象设计。
不可变对象最核心的地方在于不给其他线程修改共享资源的机会,达到不加锁也能避免多线程情况下的数据不一致的问题。
其本质就是将线程共享的资源变成线程私有。不存在共享资源,也就不能存在线程安全导致的数据不一致问题。
二、实现案例
实现一个累加器作为讲解不可变对象设计
2.1、线程不安全的累加器
2.1.1、实现代码如下
/**
* <p> 线程不安全的 int 类型累加器 </p>
*
* @author zhixing
*/
public class UnSafeIntegerAccumulator {
/** 值 **/
@Getter
private int value;
/** 构造函数,并制定初始值 **/
public UnSafeIntegerAccumulator(int init) {
this.value = init;
}
/** 进行累加操作 **/
public int add(int i){
this.value += i;
return this.value;
}
}
2.1.2、演示线程安全问题
如上述代码,定义三个线程进行数值的累加操作。
其中共享资源为 value ,在操作过程中,该值没有进行任何处理,所以存在线程安全问题导致的数据不一致问题。
2.1.3、通过加 synchronized 解决线程安全问题
如上述代码,针对累加操作进行加锁锁定,使得在同一时刻只能有一个线程对共享资源进行访问(Single Thread Execution)。
2.2、线程安全的累加器(不可变对象设计模式)
2.2.1、实现代码如下
/**
* <p> 线程安全的 int 类型累加器(不可变对象设计模式) </p>
*
* class final 类不允许被继承
*
* @author zhixing
*/
public final class SafeIntegerAccumulator {
/** 值 **/
@Getter
private final int value;
/** 构造函数,并制定初始值 **/
public SafeIntegerAccumulator(int init) {
this.value = init;
}
/** 构造新的累加器 **/
public SafeIntegerAccumulator(SafeIntegerAccumulator accumulator,int init){
this.value = accumulator.getValue() + init;
}
/** 进行累加操作 **/
public SafeIntegerAccumulator add(int i){
return new SafeIntegerAccumulator(this,i);
}
}
如上述代码所示,不可变对象设计模式能够通过无锁实现线程安全的原因如下:
- 1、class 增加 final,防止被继承重写导致失去线程安全性
- 2、属性 value 被 final 修饰,不允许线程对其进行改变,在构造函数赋值后,无法再进行修改。
总的来说,不可变对象的设计原理,就是每一个线程所持有的值,不再是一个共享的资源,而是线程独有的,所以不存在线程安全问题。