线程共享对象是引起线程安全的原因,所以怎么发布对象至关重要。
package com.accat.concurrency.example.publish;
import com.accat.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@Slf4j
@NotThreadSafe
public class UnsafePublish {
private String[] states = {"a", "b", "c"};
public String[] getStates() {
return states;
}
public static void main(String[] args) {
UnsafePublish unsafePublish = new UnsafePublish();
log.info("{}", Arrays.toString(unsafePublish.getStates()));
unsafePublish.getStates()[0] = "d";
log.info("{}", Arrays.toString(unsafePublish.getStates()));
}
}
这里通过
getter
获取对象后,任何其他对象或线程都可以直接操作states
,所以是不安全的。
package com.accat.concurrency.example.publish;
import com.accat.concurrency.annoations.NotRecommend;
import com.accat.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {
private int thisCanBeEscape = 0;
public Escape () {
new InnerClass();
}
private class InnerClass {
public InnerClass() {
log.info("{}", Escape.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
new Escape();
}
}
调用
Escape()
构造Escape
类,而构造过程包含一个内部类InnerClass
,其构造函数使用Escape.this.thisCanBeEscape
,这个过程中有可能Escape还没有构造完成,如果在多线程程序中有可能线程访问快于类本身的构造,从而引起线程安全问题。
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.NotThreadSafe;
/**
* 懒汉模式
* 单例实例在第一次使用时进行创建
*/
@NotThreadSafe
public class SingletonExample1 {
// 私有构造函数
private SingletonExample1() {
}
// 单例对象
private static SingletonExample1 instance = null;
// 静态的工厂方法
public static SingletonExample1 getInstance() {
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
懒汉模式
线程不安全,因为多线程的情况下,有可能多个线程同时调用getInstance()
, 从而导致进行多次初始化操作。
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.ThreadSafe;
/**
* 饿汉模式
* 单例实例在类装载时进行创建
*/
@ThreadSafe
public class SingletonExample2 {
// 私有构造函数
private SingletonExample2() {
}
// 单例对象
private static SingletonExample2 instance = new SingletonExample2();
// 静态的工厂方法
public static SingletonExample2 getInstance() {
return instance;
}
}
饿汉模式
解决了懒汉模式的线程安全问题,它的不足在于如果初始化的操作过多,有可能导致启动时间更长。
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.NotThreadSafe;
/**
* 懒汉模式 -》 双重同步锁单例模式
* 单例实例在第一次使用时进行创建
*/
@NotThreadSafe
public class SingletonExample4 {
// 私有构造函数
private SingletonExample4() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// JVM和cpu优化,发生了指令重排
// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象
// 单例对象
private static SingletonExample4 instance = null;
// 静态的工厂方法
public static SingletonExample4 getInstance() {
if (instance == null) { // 双重检测机制 // B
synchronized (SingletonExample4.class) { // 同步锁
if (instance == null) {
instance = new SingletonExample4(); // A - 3
}
}
}
return instance;
}
}
双重检测机制
从懒汉模式改造过来,规避线程不安全的问题,但是这里存在另外一个问题。
instance = new SingletonExample4()
指令有可能被JVM重排。
然后导致多个线程执行多次instance = new SingletonExample4()
。
// 单例对象 volatile + 双重检测机制 -> 禁止指令重排
private volatile static SingletonExample5 instance = null;
解决的方法是加上
volatile
关键字,禁止指令重排(Volatile适用场景二)
推荐写法
package com.accat.concurrency.example.singleton;
import com.accat.concurrency.annoations.Recommend;
import com.accat.concurrency.annoations.ThreadSafe;
/**
* 枚举模式:最安全
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {
// 私有构造函数
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample7 singleton;
// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}
兼并懒汉模式和饿汉模式的优点,只有在对象要被使用时才创建,又保证了线程安全性。