首先创建单例模式要符合三个条件:1、一个类只能创建一个对象;2、该类自己创建这个对象;3、提供全局对外生成对象的方法。
1、饿汉模式
public final class Singleton {
private static Singleton instance=new Singleton();//该类自己创建实例
private Singleton(){}//构造函数私有
public static Singleton getInstance(){//通过公共方法对外提供服务
return instance;
}
}
分析:饿汉模式可以保证多线程下的生成对象是唯一的,原因是在类加载阶段执行
2、懒汉模式
懒汉模式中的懒即懒加载,即对象使用时加载,避免在类加载的过程中占用内存空间来加载类对象,节省内存空间。
public final class Singleton {
private static Singleton instance= null;
private Singleton(){}//构造函数
public static Singleton getInstance(){//通过该方法对外提供生成单例对象
if(null == instance){//当instance为null时,则实例化对象
instance = new Singleton();
}
return instance;//否则返回已经存在的对象
}
}
上述实现方式在单线程下是不会有问题的,但是在多线程情况下是线程不安全的。分析原因如下:假设线上A通过 if 判断 instance == null 的时候,线程B也执行到了该处代码,那么线程A和线程B就会生成两个对象,破坏了单例的定义。
基于此需要通过synchronized来加上同步锁,来避免多线程访问的时候创建多个对象,代码示例如下:
public final class Singleton {
private static Singleton instance= null;
private Singleton(){}//构造函数
public static Synchronized Singleton getInstance(){//通过该方法对外提供生成单例对象
if(null == instance){//当instance为null时,则实例化对象
instance = new Singleton();
}
return instance;//否则返回已经存在的对象
}
}
加上同步锁以后,势必会降低产生对象的效率,另外每次请求创建对象的时候,只有在第一获取的时候需要创建对象,其他请求基本都不是null,并且产生多个对象是因为 if 判断处导致的,基于以上两点考虑如下实现方式:
public final class Singleton {
private static Singleton instance= null;
private Singleton(){}//构造函数
public static Synchronized Singleton getInstance(){//通过该方法对外提供生成单例对象
if(null == instance){//当instance为null时,则实例化对象
Synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;//否则返回已经存在的对象
}
}
但是以上实现还是有问题,依然会创建多个实例。这是由于当多个线程进入到 if 判断条件里,虽然有了synchronized同步锁,但是进入到判断条件里面的线程依然会依次获取到锁创建对象,然后再释放同步锁。所以还需在同步锁里面加一个判断条件即Double-check,代码示例如下:
public final class Singleton {
private static Singleton instance= null;
private Singleton(){}//构造函数
public static Synchronized Singleton getInstance(){//通过该方法对外提供生成单例对象
if(null == instance){//当instance为null时,则实例化对象
Synchronized(Singleton.class){
if(null == instance){
instance = new Singleton();
}
}
}
return instance;//否则返回已经存在的对象
}
}
上述代码当出现指令重排序的时候,依然会存在问题,问题主要出现在 instance = new Singleton(); 的时候,正常的过程是 分配内存空间、属性初始化、对象执行内存空间。JVM为了提高并发效率可能破坏上述过程,导致多线程情况下获取的对象可能还没有完成初始化,进而导致属性不可用。这时候的synchronized同步锁可以保证有序性,可见性,但是不能保证有序性。
volatile可以弥补synchronized的空缺, volatile变量的操作指令都不会被重排序。于是有了如下代 码示例:
public final class Singleton {
private volatile static Singleton instance= null;
private Singleton(){}//构造函数
public static Synchronized Singleton getInstance(){//通过该方法对外提供生成单例对象
if(null == instance){//当instance为null时,则实例化对象
Synchronized(Singleton.class){
if(null == instance){
instance = new Singleton();
}
}
}
return instance;//否则返回已经存在的对象
}
}
上述实现单例的方式比较复杂容易出错。日常不推荐使用,推荐使用内部类的方式或者枚举的方式实现
内部类的方式可以避免多线程下重复创建对象的发生,并且在第一次调用生成对象方法的时候创建对象,代码示例如下:
public final class Singleton {
private Singleton() {
}
// 内部类实现
public static class InnerSingleton {
private static Singleton instance=new Singleton();
}
public static Singleton getInstance() {
return InnerSingleton.instance;
}
}
另外枚举天生是单例的,常用枚举属于饿汉式,下面提供枚举方式实现懒汉式示例代码:
public class Sinleton {
private static Sinleton instance = null;
// 私有构造函数
private Sinleton() {
}
public static Sinleton getInstance() {
return SinletonEnum.SINLETON.getInstance();
}
private enum SinletonEnum {
SINLETON;
private Sinleton singleton;
// JVM保证这个方法只调用一次
SinletonEnum() {
singleton = new Sinleton();
}
public Sinleton getInstance() {
return singleton;
}
}
}



