什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 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() 实例化。
测试代码:
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)
判断语句中,最后各自都创建了对象。
既然多个线程可以同时进入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也是大多数多线程结合单例模式使用的解决方案。
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 复制代码
本文使用若干案例来阐述单例模式与多线程结合遇到的情况与解决方案。