# JAVA并发编程(三)-单例模式
单例(singleton)模式需要实现的效果是保持一个类有且仅有一个实例,出于性能考虑,一般都会使用懒加载的模式,只有在使用到的时候才会去创建实例.
## 单线程版本的单例模式
```java
public class SingleThreadSingleton {
private static SingleThreadSingleton singleThreadSingleton = null;
private SingleThreadSingleton(){}
public static SingleThreadSingleton getInstance() {
if (singleThreadSingleton == null) {
return new SingleThreadSingleton();
}
return singleThreadSingleton;
}
}
```
在多线程的环境下,这种写法不能生效,因为有一个if语句形成了check-then-act操作,它不是一个原子操作,可能线程T1执行new SingleThreadSingleton() 的时候,线程T2在执行if判断,尽管T1此时已经在创建一个实例了,但是T2判断还是为null,需要创建一个新的实例,导致多个实例被创建,不是正确的单例模式
## 简单加锁的单例模式
```java
public class SingleMulThreadLockThread {
private static SingleMulThreadLockThread instance = null;
private SingleMulThreadLockThread(){}
public static SingleMulThreadLockThread getInstance(){
synchronized (SingleMulThreadLockThread.class) {
if (instance == null) {
return new SingleMulThreadLockThread();
}
return instance;
}
}
}
```
这种方法是简单而又可靠的,但是每次获取实例时,都需要申请锁,导致了开销增大
## 错误的双重检查锁定的单例模式
```java
public class SingleMulThreadDCLThread {
private static SingleMulThreadDCLThread instance = null;
private SingleMulThreadDCLThread(){}
public static SingleMulThreadDCLThread getInstance(){
if (null == instance){//语句①
synchronized (SingleMulThreadDCLThread.class) {
if (null == instance) {//语句②
return new SingleMulThreadDCLThread();//语句③
}
}
}
return instance;
}
}
```
这种方法看似很好,首先判断instance是否是null,如果是的话,再去获取锁,假如T1执行到语句①,发现是null,而此时线程T2执行到语句③,此时线程T1根据锁的原子性与可见性来看,执行到语句②时发现已经不为null了,则可以直接返回.避免了创建多个实例.但是这种写法还存在问题,在synchronized的代码块中,代码是具有原子性,可见性,有序性的,但是临界区内的代码也是可以重排序的,语句③可能会被重排序成
```java
objRef = allocate(SingleMulThreadDCLThread.class);//申请空间,正常顺序①
instance = objRef;//将对象写入共享变量,正常顺序③
invokeConstructor(objRef);//将对象初始化,正常顺序②
```
但是在语句①处,可能会存在这种情况.导致我们在第一次检查时,发现instance并不等于null,但是该变量引用的对象并没有完全初始化,导致返回的实例是错误的.
## 正确的双重检查锁定的单例模式
```java
public class SingleMulThreadDCLThread {
private static volatile SingleMulThreadDCLThread instance = null;
private SingleMulThreadDCLThread(){}
public static SingleMulThreadDCLThread getInstance(){
if (null == instance){
synchronized (SingleMulThreadDCLThread.class) {
if (null == instance) {
return new SingleMulThreadDCLThread();
}
}
}
return instance;
}
}
```
只需要将instance变量使用volatile关键词修饰即可,这个保证处理器不会按照错误的顺序来初始化对象,使线程获取到的对象一定是初始化完毕的.
## 基于静态内部类实现的单例模式
```java
public class StaticSingleton {
private StaticSingleton(){}
static class InstanceHolder{
final static StaticSingleton INSTANCE = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return InstanceHolder.INSTANCE;
}
}
```
因为静态变量首次被访问时会触发虚拟机对类进行初始化,该类的静态变量就可以为初始值而非默认值,因此静态方法getInstance()被调用时,java虚拟机会初始化静态内部类InstanceHolder,使得StaticSingleton的变量被创建,而类的静态变量只会被创建一次,因此StaticSingleton也只会有一个实例.
## 枚举类型的单例模式实现
```java
public enum SingletonEnum {
INSTANCE;
SingletonEnum(){}
public void someService(){
System.out.println("some service");
}
}
```
枚举类型SingletonEnum相当于一个单例类,而字段INSTANCE是该类的唯一实例,而且这个实例只有在初次引用时才会被初始化.
JAVA并发编程(三)-单例模式