https://blog.csdn.net/Evan_QB/article/details/83537766
https://blog.csdn.net/liuchaoxuan/article/details/79840013 参考
https://blog.csdn.net/weixin_42613513/article/details/84844317 线程命名测试运行
饿汉式,安全的
public class SingletonExample2 { //私有化构造函数 private SingletonExample2(){ } //单例对象 private static SingletonExample2 instance = new SingletonExample2(); //静态工厂方法 public static SingletonExample2 getInstance(){ return instance; } }
懒汉式 不安全的
public class SingletonExample1 { //私有化构造函数 private SingletonExample1(){ } //单例对象 private static SingletonExample1 instance = null; //静态工厂方法 public static SingletonExample1 getInstance(){ if (instance == null){ instance = new SingletonExample1(); } return instance; }
从写法上我们可以看出,饿汉模式是线程安全的,但它的性能上会大大折扣。那么我们能否也让懒汉模式也变得线程安全呢?答案是可以的
方法一.在方法上加上同步锁
直接在获取实例的方法上加上synchronized关键字
public class SingletonExample3 { //私有化构造函数 private SingletonExample3(){ } //单例对象 private static SingletonExample3 instance = null; //静态工厂方法 public synchronized static SingletonExample3 getInstance(){ if (instance == null){ instance = new SingletonExample3(); } return instance; } }
虽说该方法是线程安全的,但其性能也和饿汉模式差不多,在性能上会大大折扣,别急我们接着看
方法二.使用双重校验加同步锁机制
public class SingletonExample4 { //私有化构造函数 private SingletonExample4(){ } //指令重排问题: //1.分配内存空间 //2.初始化对象 //3.instance = memory设置instance指向刚分配的内存 //单例对象 private static SingletonExample4 instance = null; //静态工厂方法 public static SingletonExample4 getInstance(){ if (instance == null){ //双重检测机制 synchronized (SingletonExample4.class) { //同步锁 if (instance == null){ instance = new SingletonExample4(); } } } return instance; } }
通过两次判断,确保创建的对象只能有一个,但这种方法还是存在线程安全的问题的。
在单线程的情况下,以上的代码没有丝毫问题,但在多线程的情况下,就会存在指令重排问题
当A、B线程达到以上位置时,发生指令重排,在A线程执行到指令2(将对象的引用指向新分配的空间)时,刚好CPU被B占用,这样B的对象指向了一个内存空间,但其对象并没有被实例化
我们可以给代码加上一个volatile关键字来防止指令重排
//单例对象 private static volatile SingletonExample4 instance = null;
方法三.使用枚举模式来创建对象
有了以上方法为何还会需要第三种方法呢?那是因为java中还有一种暴力的创建方法,反射,虽然不能通过关键字new来创建对象,但通过反射创建的对象,就不会是单例的了,那么有什么办法可以解决吗?答案是有的,就是使用枚举。
public class SingletonExample7 { private SingletonExample7(){} public static SingletonExample7 getInstance(){ return Singleton.INSTANCE.getInstance(); } private enum Singleton{ INSTANCE; private SingletonExample7 singleton; //JVM保证这个方法绝对只调用一次 Singleton(){ singleton = new SingletonExample7(); } public SingletonExample7 getInstance() { return singleton; } } }
4、使用静态内置类实现单例模式
DCL解决了多线程并发下的线程安全问题,其实使用其他方式也可以达到同样的效果,代码实现如下:
package org.mlinge.s06; public class MySingleton { //内部类 private static class MySingletonHandler{ private static MySingleton instance = new MySingleton(); } private MySingleton(){} public static MySingleton getInstance() { return MySingletonHandler.instance; } }
测试1:继承线程
public class Singleton { //防止指令重排 private static volatile Singleton singleton =null; private Singleton(){ } public static Singleton getSingleton() { if (singleton == null) {//双重检测 synchronized (Singleton.class) { if (singleton==null) { singleton = new Singleton(); } } } return singleton; } } public class SingletonTest extends Thread{ public static void main(String ss[]){ Thread[] tms = new Thread[10]; for (int i = 0;i<tms.length;i++){ SingletonTest singletonTest = new SingletonTest(); singletonTest.setName("线程"+i); tms[i] = singletonTest; } for (int j = 0;j<tms.length;j++){ tms[j].start(); } } @Override public void run(){ System.out.println(Thread.currentThread().getName()+"_"+Singleton.getSingleton().hashCode()); }
测试2:实现runable
public class Singleton { //防止指令重排 private static volatile Singleton singleton =null; private Singleton(){ } public static Singleton getSingleton() { if (singleton == null) {//双重检测 synchronized (Singleton.class) { if (singleton==null) { singleton = new Singleton(); } } } return singleton; } } public class SingletonRunableTest implements Runnable { public static void main(String ss[]){ Thread[] tms = new Thread[10]; for (int i = 0;i<tms.length;i++){ SingletonRunableTest test = new SingletonRunableTest(); tms[i] = new Thread(test,"线程"+i); } for (int j = 0;j<tms.length;j++){ tms[j].start(); } } @Override public void run() { System.out.println(Thread.currentThread().getName()+"_"+Singleton.getSingleton().hashCode()); } }