public class Main1 {
public static void main(String[] args) {
DoubleCheck instance = DoubleCheck.getInstance();
DoubleCheck instance1 = DoubleCheck.getInstance();
System.out.println(instance == instance1);
}
}
/**
* 1.单例饿汉模式
私有化构造方法
静态常量,提前创建好对象实例
方法返回对象实例
*/
//提前创建的实例可能不会被使用到,导致浪费内存
class Singleton {
private Singleton(){
}
public static final Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
/**
* 2.懒汉模式
* 私有构造方法
* 什么时候需要实例什么时候创建
* 且只有第一次时创建
*/
//并发时,可能判断均为Null,new了两个实例,不满足单例
class Lazy{
private Lazy(){
}
public static Lazy instance;
public static Lazy getInstance() {
if (instance == null){
instance = new Lazy();
}
return instance;
}
}
/**
* 3.双重检测机制(推荐)
*/
public class Main2
{
private static volatile Singleton instance;//4.防止重排序
static class Singleton{
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){//1.为了并发;
synchronized (new Singleton()){//2.为了单例
if (instance == null){//3.为了单例
instance = new Singleton();
}
}
}
return instance;
}
}
public static void main(String[] args)
{
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1);
}
}
/**
* 1.当t1,t2,t3都来了,此时instance已存在,则可以立即根据此if快速判断,然后返回已存在的instance;
* 如果没有这个if,其他不变,就会导致t1,t2,t3都需要获得锁,进入同步代码块中才能判断出是否instance已经被创建过了,这样会有大量积压
*
* 2.第一instace不存在,需要一个线程去创建,只能一个线程进入代码块去创建实例
*
* 3.此时t1,t2,t3都突破了第一层if判断,三者都认为instance实例还未被创建。比如t2获取了锁,如果没有第二层if判读的话,t2会直接创建一个实例,
* 这样不管不顾的做法,完全没有判断在t2之前,是否t1已经获取过锁进入了代码块中已经创建好了instance实例
*
* 4.如果发生了重排序会导致拿到的单例,是未被初始化的单例
*
**
//volatile:防止指令重排序
/**
* instance = new Singleton();分为三个动作
* 1.堆内存开辟一片空间,比如0X01
* 2.堆中创建Singleton,有初始化赋值的话,赋值
* 3.将对象内存地址返回给对象引用变量instance,instance存有0X01,instance在栈帧的局部变量表中
*
* 因为cpu为了提高吞吐量,会自动的改变cpu流水线,即指令的操作先后顺序改变,对单线程没有影响,多线程有
* eg: 执行顺序 2,3变成让你3,2,导致返回给instance引用对象地址,虽然不为Null,但是根本没有完成赋值
* 导致t1执行同步代码执行结束的时候,t2判读,Instance!=null,但是instance本身没赋值;t2会直接返回instance
*/
Spring5源码中很多这种单例实现模式
静态内部类
public class SkuDTO {
private SkuDTO(){}
private static class SkuDTOHolder {
private static final SkuDTO INSTANCE = new SkuDTO();
//静态内部类不会再外部类被JVM加载到内存的时候一并被加载。什么时候调用什么时候加载,解决了饿汉问题
//JVM本身保证了SkuDTO只会在被类加载器加载时初始化一次,所以是线程安全的
}
public static SkuDTO getInstance() {
return SkuDTOHolder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(
() -> System.out.println(getInstance().hashCode())//都是同一个对象
).start();
}
}
}