单一赋值可以,但是含复合运算赋值不可以(i++ 之类)
UseVolatileDemo.java
volatile int a = 10;
volatile boolean flag = false;
状态标志,判断业务是否结束
UseVolatileDemo.java
/**
* 使用:作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或任务结束
* 理由:状态标志并不依赖于程序内任何其他状态,且通常只有一种状态转换
* 例子:判断业务是否结束
*/
public class UseVolatileDemo {
private volatile static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while(flag) {
//do something......
}
},"t1").start();
try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
flag = false;
},"t2").start();
}
}
开销较低的读,写锁策略
UseVolatileDemo.java
public class UseVolatileDemo {
/**
* 使用:当读远多于写,结合使用内部锁和 volatile 变量来减少同步的开销
* 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性
*/
public class Counter {
private volatile int value;
public int getValue() {
return value; //利用volatile保证读取操作的可见性
}
public synchronized int increment() {
return value++; //利用synchronized保证复合操作的原子性
}
}
}
DCL 双端锁
问题
SafeDoubleCheckSingleton.java
public class SafeDoubleCheckSingleton {
private static SafeDoubleCheckSingleton singleton;
//私有化构造方法
private SafeDoubleCheckSingleton() {
}
//双重锁设计
public static SafeDoubleCheckSingleton getInstance() {
if (singleton == null){
//1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
synchronized (SafeDoubleCheckSingleton.class){
if (singleton == null){
//隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
singleton = new SafeDoubleCheckSingleton();
}
}
}
//2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
return singleton;
}
}
-
单线程环境下会执行如下操作,保证能获取到已完成初始化的实例
memory = allocate(); // 1:分配对象的内存空间 ctorInstance(memory); // 2:初始化对象 instance = memory; // 3:设置 instance 指向刚分配的内存地址
-
多线程环境下,由于重排序导致 2,3 乱序,导致其他线程得到的是 null 而不是完成初始化的对象
memory = allocate(); // 1:分配对象的内存空间 instance = memory; // 3:设置 instance 指向刚分配的内存地址 // 注意,此刻对象还没有初始化 ctorInstance(memory); // 2:初始化对象
这种场景在著名的双重检锁(double-checked-locking)中会出现:
// 注意 volatile private volatile static Singleton instance; public static Singleton getInstance { // 第一次 null 检查 if (instance == null) { synchronized(Singleton.class) { // 1 // 第二次 null 检查 if (instance == null) { // 2 instance = new Singleton(); // 3 } } } return instance; }
其中第3步中实例化 Singleton 分多步执行(分配内存空间、初始化对象、将对象指向分配的内存空间)某些编译器为了性能原因,会将第二步和第三步进行重排序(分配内存空间、将对象指向分配的内存空间、初始化对象)。这样某个线程可能会获得一个未完全初始化的实例。
解决方案
- 加 volatile 修饰
SafeDoubleCheckSingleton.java
public class SafeDoubleCheckSingleton {
//通过volatile声明,实现线程安全的延迟初始化。
private volatile static SafeDoubleCheckSingleton singleton;
//私有化构造方法
private SafeDoubleCheckSingleton(){
}
//双重锁设计
public static SafeDoubleCheckSingleton getInstance(){
if (singleton == null){
//1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
synchronized (SafeDoubleCheckSingleton.class){
if (singleton == null){
//隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
//原理:利用volatile,禁止 "初始化对象"(2) 和 "设置singleton指向内存空间"(3) 的重排序
singleton = new SafeDoubleCheckSingleton();
}
}
}
//2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
return singleton;
}
}
- 采用静态内部类的方式实现
SingletonDemo.java
public class SingletonDemo {
private SingletonDemo() { }
private static class SingletonDemoHandler {
private static SingletonDemo instance = new SingletonDemo();
}
public static SingletonDemo getInstance() {
return SingletonDemoHandler.instance;
}
}
静态内部类知识补充
静态内部类的实现方式具有几个特点:
- 独立性:静态内部类可以独立于外部类的实例存在,即使外部类没有被实例化,也可以创建静态内部类的对象。
- 访问权限:静态内部类可以访问外部类的所有静态成员,包括私有的静态成员。但是,它不能直接访问外部类的非静态成员。
- 使用场景:静态内部类通常用于当内部类的行为不依赖于外部类实例时,或者用于构造单例模式等。
OuterClass.java
public class OuterClass {
private static String staticOuterVariable = "外部类的静态变量";
private String instanceOuterVariable = "外部类的实例变量";
// 静态内部类
public static class StaticInnerClass {
public void display() {
// 访问外部类的静态变量
System.out.println(staticOuterVariable);
// 不能直接访问外部类的实例变量
// System.out.println(instanceOuterVariable); // 这会产生编译错误
}
}
public static void main(String[] args) {
// 直接创建静态内部类的实例,无需外部类的实例
OuterClass.StaticInnerClass innerObject = new OuterClass.StaticInnerClass();
innerObject.display();
}
}
在这个例子中,StaticInnerClass
是一个静态内部类,它可以访问外部类OuterClass
的静态成员staticOuterVariable
,但是不能访问实例成员instanceOuterVariable
。要创建静态内部类的实例,我们不需要OuterClass
的实例,直接使用OuterClass.StaticInnerClass
即可创建。