什么是单例模式:
使用一个实例处理处理各种操作,而不是对应每一个操作都会创建一个实例。因为都是用一个实例那么这个实例自然被线程共享了,那么就会出现线程安全问题。
单例模式两种方式:
饿汉模式,懒汉模式。
优缺点:
饿汉模式优点是一开始就被初始化了,不会出现线程问题,缺点是在不使用这个实例时也会创建浪费内存资源,同时也无法适用于需要参数生成实例的场景。
懒汉模式使用才会被创建优点是可以传参,使用才会创建不浪费内存资源但是线程不安全。
Java中6种创建单例模式的方式
1、普通的懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
并发编程的时候这种会出现线程安全问题
2、加锁的懒汉模式
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
这种做法虽然线程安全了,但是降低了效率,每次这个实例只能被一个线程调用。
3、双重检验锁
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
为什么叫双重检查锁,在锁前和锁后都进行了判空操作,因为在第一个if可能进来两个线程先后去获得锁,那么就会创建两个实例。实例使用volatile修饰的原因是instance = new Singleton(); 不是一个原子操作。它可以拆解为三步。
a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址
上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接返回一个未初始化的对象。
4、饿汉式 static final field
public class Singleton{ |
缺点是在不使用这个实例时也会创建浪费内存资源,同时也无法适用于需要参数生成实例的场景。
5、静态内部类懒汉模式:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
6、枚举
public enum EasySingleton{ |
创建枚举默认就是线程安全的,所以不需要担心double checked locking。
引用