何谓单例模式?对单例模式的解释是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
在创建单例模式时,无非就是使用饿汉式或懒汉式进行单例创建,而在使用中也包含非线程安全和线程安全。
问:你知道吗?
单例模式不仅仅只是普遍的饿汉式和懒汉式,其实可以分出8种创建方式!
什么?!8种创建单例模式的方式?!
哈哈,相信看到这里大家都想知道是怎样得到8种了吧!好了,别着急~以下为大家一一解答~~~
单例模式1:饿汉式
//单例模式1:饿汉式
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}
相信大家对饿汉式都是比较了解的吧!在开发中也没少用~~~
单例模式2:懒汉式(非线程安全)
//单例模式2:懒汉式(非线程安全)
public class Singleton2 {
private static Singleton2 instance = null;
private Singleton2(){}
public static Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
这种非线程安全的懒汉式,相信普遍的初学者都会使用这种单例模式进行开发~~~
单例模式3:懒汉式(同步方法,线程安全)
//单例模式3:懒汉式(同步方法,线程安全)
public class Singleton3 {
private static Singleton3 instance = null;
private Singleton3(){}
//synchronized 修饰getInstance方法会导致很多不必要的阻塞
public static synchronized Singleton3 getInstance(){
if(instance == null){
instance = new Singleton3();
}
return instance;
}
}
使用同步方法synchronized修饰获取类的单例,虽然线程安全,但在使用过程中会造成很多不必要的阻塞!
单例模式4:懒汉式(双锁,线程安全,但无法解决生成汇编代码时指令重排的问题)
//单例模式4:懒汉式(双锁,线程安全,无法解决生产汇编代码时指令重排的问题)
public class Singleton4 {
private static Singleton4 instance = null;
private Singleton4(){}
public static Singleton4 getInstance(){
if(instance == null){
synchronized (Singleton4.class){
if(instance == null){
instance = new Singleton4();
}
}
}
return instance;
}
}
这里添加了双重线程锁对单例模式进行更一步安全的防护,生成汇编代码时指令重排请自行了解volatile修饰符!
单例模式5:懒汉式(双锁,线程安全,volatile修饰优点是只有在第一次实例化时加锁,之后不会加锁;生成汇编代码时指令重排)
//单例模式5:懒汉式(双锁,线程安全,volatile修饰优点是只有在第一次实例化时加锁,之后不会加锁;生产汇编代码时指令重排)
public class Singleton5 {
private static volatile Singleton5 instance = null;
private Singleton5(){}
public static Singleton5 getInstance(){
if(instance == null){
synchronized (Singleton5.class){
if(instance == null){
instance = new Singleton5();
}
}
}
return instance;
}
}
volatile修饰类变量,一旦类变量发生改变会立即同步。
单例模式6:懒汉式(双锁,线程安全,volatile修饰,局部实例减少与主内存的交互次数)
//单例模式6:懒汉式(双锁,线程安全,volatile修饰,局部实例减少与主内存的交互次数)
public class Singleton6 {
private static volatile Singleton6 instance = null;
private Singleton6(){}
public static Singleton6 getInstance(){
//读取一次主内存,后序没有读取主内存的操作了
Singleton6 result = instance;
if(result == null){
synchronized (Singleton6.class){
if(result == null){
instance = result = new Singleton6();
}
}
}
return instance;
}
}
因为volatile操作的是主内存的数据,主内存速度比工作内存的慢,所以可以设置一个局部实例在工作内存,以此减少与主内存的交互次数。
在此,顺便提一下著名的DCL失效问题(双重锁懒汉模式(Double Check Lock)):在new创建对象时,其实在jvm里面的执行分为三步:1.在堆内存开辟内存空间=》2.在堆内存中实例化SingleTon里面的各个参数=》3.把对象指向堆内存空间。由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到其他线程上,由于执行了3,对象已经非空了,会被直接拿出来用,这样的话,就会出现异常,也就会导致DCL出现线程不安全的情况。该问题在JDK1.5之后得以解决:只要将对象定义为“private volatile static SingleTon instance = null”,就可解决DCL失效问题,因为volatile只是在线程内存和main memory(主内存)间同步某个变量的值,因此确保instance每次均在主内存中读取,这样定义会消耗一点资源,但并不会造成什么影响。
单例模式7:懒汉式(静态内部类,线程安全)
//单例模式7:懒汉式(静态内部类,线程安全)
public class Singleton7 {
private static class Singleton7Holder{
public static final Singleton7 instance = new Singleton7();
}
private Singleton7(){}
public static Singleton7 getInstance(){
return Singleton7Holder.instance;
}
}
利用静态内部类(只有在出现它的引用时才被加载),完成懒加载,final保证线程安全。
单例模式8:通过枚举实现(单例,线程安全,防止反序列化产生新对象,防止反射攻击)
//单例模式8:通过枚举实现(单例,线程安全,防止反序列化产生新对象,防止反射攻击)
public enum Singleton8 {
instance;
private String attribute;
public void setAttribute(String attribute){
this.attribute = attribute;
}
public String getAttribute() {
return attribute;
}
}
枚举类型特点:枚举类就是class,而且是一个不可被继承的final类;枚举量(instance)是类静态常量;假构造器,底层没有无参数的构造器;所有枚举常量都是通过静态代码来初始化,即在类加载期间就初始化;不能通过克隆,不能通过序列化和反序列化来复制枚举,保证一个枚举常量只是一个实例(保证单例)。
注:如果单例模式不是用枚举类创建的,那么其无法防止其序列化过程破坏单例特性,因为在序列化过程会利用反射调用单例的私有构造方法生成新的对象,如果要避免这种破坏,就要在单例类里面添加方法readResolve(),具体参考:http://mp.weixin.qq.com/s/iXC47w4tMfpZzTNxS_JQOw