本文只须要考虑一件事:如何使单例模式遇到多线程是安全的、正确的html
什么是当即加载?当即加载就是使用类的时候已经将对象建立完毕,常见的实现办法就是直接 new 实例化。编程
public class MyObject { private static MyObject myObject = new MyObject(); public MyObject(){ } public static MyObject getInstance(){ return myObject; } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
打印结果:设计模式
985396398 985396398 985396398
控制台打印的 hashCode 是同一个值,说明对象是同一个,也就实现了当即加载型单例设计模式。安全
此版本的缺点是不能有其余其余实例变量,由于getInstance()方法没有同步,因此有可能出现非线程安全问题。多线程
什么是延迟加载?延迟加载就是在调用 get() 方法时实例才被建立,常见的实现方法就是在 get() 方法中进行 new() 实例化。并发
测试代码:ide
public class MyObject { private static MyObject myObject; public MyObject() { } public static MyObject getInstance() { try { if (myObject == null) { //模拟对象在建立以前作的一些准备工做 Thread.sleep(3000); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
打印结果:测试
985396398 610025186 21895028
从运行结果来看,建立了三个对象,并非真正的单例模式。缘由显而易见,3个线程同时进入了if (myObject == null)
判断语句中,最后各自都建立了对象。.net
既然多个线程能够同时进入getInstance() 方法,那么只须要对其进行同步synchronized处理便可。线程
public class MyObject { private static MyObject myObject; public MyObject() { } synchronized public static MyObject getInstance() { try { if (myObject == null) { //模拟对象在建立以前作的一些准备工做 Thread.sleep(3000); myObject = new MyObject(); } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
打印结果:
961745937 961745937 961745937
虽然运行结果代表,成功实现了单例,但这种给整个方法上锁的解决方法效率过低。
同步方法是对方法的总体加锁,这对运行效率来说很不利的。改为同步代码块后:
public class MyObject { private static MyObject myObject; public MyObject() { } public static MyObject getInstance() { try { synchronized (MyObject.class) { if (myObject == null) { //模拟对象在建立以前作的一些准备工做 Thread.sleep(3000); myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
打印结果:
355159803 355159803 355159803
运行结果虽然代表是正确的,但同步synchronized语句块依旧把整个 getInstance()方法代码包括在内,和synchronize 同步方法效率是同样低下。
因此,咱们能够针对某些重要的代码进行单独的同步,而其余的代码则不须要同步。这样在运行时,效率彻底能够获得大幅度提高。
public class MyObject { private static MyObject myObject; public MyObject() { } public static MyObject getInstance() { try { if (myObject == null) { //模拟对象在建立以前作的一些准备工做 Thread.sleep(3000); synchronized (MyObject.class) { myObject = new MyObject(); } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
运行结果:
985396398 21895028 610025186
此方法只对实例化对象的关键代码进行同步,从语句的结构上来讲,运行的效率的确获得的提高。可是在多线程的状况下依旧没法解决获得一个单例对象的结果。
在最后的步骤中,使用DCL双检查锁机制来实现多线程环境中的延迟加载单例设计模式。
public class MyObject { private volatile static MyObject myObject; public MyObject() { } public static MyObject getInstance() { try { if (myObject == null) { //模拟对象在建立以前作的一些准备工做 Thread.sleep(3000); synchronized (MyObject.class) { if (myObject == null) { myObject = new MyObject(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return myObject; } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
运行结果:
860826410 860826410 860826410
使用DCL双重检查锁功能,成功地解决了“懒汉模式”遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。
volatile 在此处的做用是:
1)可见性:保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。
2)有序性:禁止进行指令重排序。当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,在其后面的操做确定尚未进行。
扩展阅读:关于volatile解决DCL(双重检查)问题的见解
DCL能够解决多线程单例模式的非线程安全问题。固然,还有许多其它的方法也能达到一样的效果。
public class MyObject { public static class MyObjectHandle{ private static MyObject myObject = new MyObject(); public static MyObject getInstance() { return myObject; } } public static MyObject getInstance(){ return MyObjectHandle.getInstance(); } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
打印结果:
1035057739 1035057739 1035057739
静态内置类能够达到线程安全问题,但若是遇到序列化对象时,使用默认的方式运行获得的结果仍是多例的。
解决方法就是在反序列化中使用readResolve()方法:
public class MyObject implements Serializable { //静态内部类 public static class MyObjectHandle{ private static final MyObject myObject = new MyObject(); } public static MyObject getInstance(){ return MyObjectHandle.myObject; } protected Object readResolve(){ System.out.println("调用了readResolve方法"); return MyObjectHandle.myObject; } public static void main(String[] args) throws IOException, ClassNotFoundException { MyObject myObject = MyObject.getInstance(); FileOutputStream outputStream = new FileOutputStream(new File("myObject.txt")); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(myObject); objectOutputStream.close(); System.out.println(myObject.hashCode()); FileInputStream inputStream = new FileInputStream(new File("myObject.txt")); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); MyObject object = (MyObject) objectInputStream.readObject(); objectInputStream.close(); System.out.println(object.hashCode()); } }
运行结果:
621009875 调用了readResolve方法 621009875
静态代码块中的代码在使用类的时候就已经执行了,因此能够应用静态代码块的这个特色来实现单例设计模式。
public class MyObject { private static MyObject myObject = null; static { myObject = new MyObject(); } public static MyObject getInstance(){ return myObject; } public static void main(String[] args){ new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
运行结果:
355159803 355159803 355159803
枚举enum 和静态代码块的特性类似,在使用枚举类时,构造方法会被自动调用,也能够应用其这个特性实现单例设计模式。
public enum Singleton { INSTANCE; private MyObject myObject = null; Singleton() { myObject = new MyObject(); } public MyObject getInstance(){ return myObject; } public static void main(String[] args){ new Thread(new Runnable() { @Override public void run() { System.out.println(Singleton.INSTANCE.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Singleton.INSTANCE.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Singleton.INSTANCE.getInstance().hashCode()); } }).start(); } }
运行结果:
1516133987 1516133987 1516133987
这样实现的一个弊端就是违反了“职责单一原则”,完善后的代码以下:
public class MyObject { public enum Singleton { INSTANCE; private MyObject myObject = null; Singleton() { myObject = new MyObject(); } public MyObject getInstance() { return myObject; } } public static MyObject getInstance(){ return Singleton.INSTANCE.getInstance(); } public static void main(String[] args){ new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println(MyObject.getInstance().hashCode()); } }).start(); } }
运行结果:
610025186 610025186 610025186
本文使用若干案例来阐述单例模式与多线程结合遇到的状况与解决方案。
《Java多线程编程核心技术》高洪岩著