多线程情况下,定时判断文本、编辑器【如CSDN、Processon等】是否修改过,如果有修改过则进行保存,类似这样的场景称为Balking模式。特点:
1、可以将表示修改的方法用synchronized或者Lock修饰
2、使用volatile修饰,没有原则性要求。比如 Dubbo负载均衡会将服务的列表同步到本地,定时查询是否发送了变化
3、还有一种无所的方式,volatile + 双重锁(Double Check)检查机制的单利实现
传统的Balking模式写法如下:
public class ClassicBalking {
boolean changed = false;
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
/**
* 启动字段保存方法
*/
void startAutoSave() {
service.scheduleWithFixedDelay(() -> {
// 自动保存
autoSave();
// 每5秒钟字自动保存一次
}, 5, 5, TimeUnit.SECONDS);
}
private void autoSave() {
synchronized (this) {
if (!changed) {
return;
}
changed = false;
}
System.out.println("我保存了");
}
private void edit() {
synchronized (this) {
changed = true;
}
}
}
线程安全问题需要注意,原子性、可见性、有序性。但是使用双重锁检查机制并不能完全解决,所以需要增加 volatile关键字修饰,如下图:
/**
* 双重锁检查方式,初始化单例, 如果想防止指令重拍序则在instance上加一个 volatile 关键字即可
* <p>
* 编译器优化【只保证在单线程下执行的结果一致】之后,可能的结果:
* 1、将多条语句位置替换
* 如: int a = 1;int b = 2; 优化为:int b = 2; int a = 1; // 单线程下是ok的
* 2、将高级语言Java的底层一条语句,对于的多条CPU指令进行重拍序下,如下面的双重锁检查
* 可能在【singleton = new Singleton();】对象时的三个指令,重拍序之后可能把2和3颠倒
* 如:1)、分配一块内存M
* 2)、在内存M上初始化Singleton对象
* 3)、将M地址赋值给instance对象
*
* <p>
* 可能的问题:
* 线程A执行到把 【M地址赋值给Instance】但是还没有真正创建对象时发生线程切换,
* 线程B在第一处的判断instance是否为null,不为null, 直接返回。使用时却发生了NPE
*
* @author kevin
* @date 2020/7/26 18:26
* @since 1.0.0
*/
public class Singleton {
/**
* 单例对象
*/
private static volatile Singleton singleton;
/**
* 对外提供的单例对象,需要保证只初始化一次,每次获取同一对象
*/
public Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
/**
* 构造私有化
*/
private Singleton() {
}
}