实现单例模式有3种方式:
- 立即加载/饿汉模式:单例实例在类装载时创建
饿汉模式是线程安全的
不足:
1、如果构造方法中存在过多的处理,会导致类加载很慢,造成性能问题
2、如果只进行类的加载没有实际调用,会造成资源浪费 - 延迟加载/懒汉模式:单例实例在第一次使用时进行创建
- 枚举模式:相较于懒汉模式线程更能保证线程安全,相较于饿汉模式是在实际调用时才被初始化(推荐使用)
实现单例模式线程安全方式:
-
方式一:饿汉模式
/** * 单例模式 * 饿汉模式:单例实例在类装载时创建 */ public class Singleton01 { private Singleton01(){} //初始化方式一:静态域 //private static Singleton01 instance = new Singleton01(); //初始化方式二:静态代码块:可以在真正创建实例化之前在静态代码块中做一些操作 private static Singleton01 instance = null; static { instance = new Singleton01(); } public static Singleton01 getInstance(){ return instance; } } //测试多线程下线程是否安全 class TestSingleThread{ public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Singleton01> c = new Callable<Singleton01>() { @Override public Singleton call() throws Exception { return Singleton01.getInstance(); } }; //创建一个定长线程池,超出的线程会在队列中等待 ExecutorService es = Executors.newFixedThreadPool(3); Future<Singleton01> f1 = es.submit(c); Future<Singleton01> f2 = es.submit(c); System.out.println(f1.get().hashCode()); System.out.println(f2.get().hashCode()); //结论:线程安全 } }
-
方式二:懒汉模式
/** * 单例模式 * 懒汉模式:单例实例在第一次使用时进行创建 */ public class Singleton { private Singleton() { } //单例对象 private static Singleton singleton = null; //private static volatile Singleton singleton = null; /* *线程安全方式一:同步代码块 */ public static Singleton getInstance(){ //方式一:效率稍低:因为这里每个等待的线程都会去拿锁 // synchronized(Singleton.class){ // if(singleton==null){ // singleton = new Singleton(); // } // return singleton; // } /** * 方式二:双重同步锁单例模式 * 但是这样实现也不完全是线程安全的,因为jvm和cpu优化,可能会导致指令重排 * 效率稍高,因为在加锁之前判断,后面的线程就不用继续等待了 */ if(singleton==null){//双重检测机制 synchronized(Singleton.class){//同步锁 if(singleton==null){//步骤a /* *新建一个Singleton内部执行的指令操作: * 1、memory = allocate() 分配对象的内存空间 * 2、ctorInstance() 初始化对象 * 3、instance = memory 设置instance指向刚分配的内存 * * jvm和cpu优化,可能会导致指令重排 * 1、memory = allocate() 分配对象的内存空间 * 3、instance = memory 设置instance指向刚分配的内存 * 2、ctorInstance() 初始化对象 * * 这样会导致线程A拿到锁之后,进入到步骤b,指令重排之后先执行了3,此时singleton已经有值了,但是还并未初始化对象, * 又有一个线程B执行到步骤a,判断到singleton不为null,则直接执行到步骤c,返回了并未初始化的singleton,则会造成问题 * * 解决指令重排:给单例对象加上volatile */ singleton = new Singleton();//步骤b } } } return singleton;//步骤c } /* *线程安全方式二:同步方法,不推荐,会造成性能问题 */ public static synchronized Singleton getInstance1(){ if(singleton==null){ singleton = new Singleton(); } return singleton; } }
-
方式三:枚举模式
/** * 单例模式 * 枚举模式:相较于懒汉模式线程更能保证线程安全,相较于饿汉模式是在实际调用时才被初始化 * 推荐使用枚举模式 */ public class Singleton02 { private Singleton02(){} public static Singleton02 getInstance(){ return Singleton.INSTANCE.getSingleton(); } private enum Singleton{ INSTANCE; private Singleton02 singleton = null; //jvm保证这个方法绝对只调用一次 Singleton(){ singleton = new Singleton02(); } public Singleton02 getSingleton(){ return singleton; } } }
-
多线程测试
新建一个测试类:public class TestSingletonThread { public static void main(String[] args) { MyThread02 t1 = new MyThread02(); MyThread02 t2 = new MyThread02(); MyThread02 t3 = new MyThread02(); t1.start(); t2.start(); t3.start(); } } class MyThread02 extends Thread{ @Override public void run() { //这里调用单例模式的类,分别打印出的hashCode一致 System.out.println(Singleton.getInstance().hashCode()); } }
打印结果: