单例模式常分为:“懒汉模式”和”饿汉模式“
懒汉模式就是立即加载,指的是在调用方法前实例对象就已经创建完成了
饿汉模式就是延迟加载,指的是在调用方法时实例对象才会被创建
常见的懒汉模式
1 public class MyObject { 2 private static MyObject myObject = new MyObject(); 3 private MyObject(){} 4 /** 5 * 该版本是立即加载 6 * 缺点是不能有其他实例变量 7 * getInstance方法没有同步 8 * 可能出现线程不安全问题 9 * @return 10 */ 11 public static MyObject getInstance(){ 12 return myObject; 13 } 14 15 public static void main(String[] args) { 16 Runnable runnable = new Runnable() { 17 @Override 18 public void run() { 19 System.out.println(com.qf.test02.MyObject.getInstance().hashCode()); 20 } 21 }; 22 Thread a = new Thread(runnable); 23 a.start(); 24 Thread b = new Thread(runnable); 25 b.start(); 26 } 27 }
------------------------------------------------------console------------------------------------------------------
635845204
635845204
实际上getInstance获得的都是同一个对象,实现了立即加载的单例设计模型
常见的懒汉模式
1 public class MyObject { 2 private static MyObject myObject ; 3 private MyObject(){} 4 5 public static MyObject getInstance(){ 6 if (myObject == null) { 7 myObject = new MyObject(); 8 } 9 return myObject; 10 } 11 12 public static void main(String[] args) { 13 14 Thread t = new Thread(new Runnable() { 15 @Override 16 public void run() { 17 System.out.println(MyObject.getInstance().hashCode()); 18 } 19 }); 20 t.start(); 21 } 22 }
------------------------------------------------------console------------------------------------------------------
1700405589
本次虽然是取得一个实例,但是在多线程环境下很可能出现多个实例的情况,与单例模式初衷相背离
模拟出现多个实例的环境:
1 public class MyObject { 2 private static MyObject myObject ; 3 private MyObject(){} 4 5 public static MyObject getInstance(){ 6 try { 7 if (myObject == null) { 8 Thread.sleep(1000); 9 myObject = new MyObject(); 10 } 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 return myObject; 15 } 16 17 public static void main(String[] args) { 18 19 for (int i = 0;i < 5;i++) { 20 Thread t = new Thread(new Runnable() { 21 @Override 22 public void run() { 23 System.out.println(MyObject.getInstance().hashCode()); 24 } 25 }); 26 t.start(); 27 } 28 } 29 }
------------------------------------------------------console------------------------------------------------------
635845204 2075012821 635845204 100758745 1833680959
出现了不同的hashcode,说明创建出了不同的对象
解决方案1:声明时采用synchronized关键字
1 public class MyObject { 2 private static MyObject myObject ; 3 private MyObject(){} 4 5 public synchronized static MyObject getInstance(){ 6 try { 7 if (myObject == null) { 8 Thread.sleep(1000); 9 myObject = new MyObject(); 10 } 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 return myObject; 15 } 16 17 public static void main(String[] args) { 18 19 for (int i = 0;i < 5;i++) { 20 Thread t = new Thread(new Runnable() { 21 @Override 22 public void run() { 23 System.out.println(MyObject.getInstance().hashCode()); 24 } 25 }); 26 t.start(); 27 } 28 } 29 }
------------------------------------------------------console------------------------------------------------------
123504074 123504074 123504074 123504074 123504074
得到了相同的实例对象。
但是这种方法采用同步运行,下一个线程想要取得对象必须等上一个线程释放对象,效率比较低
解决方案2:同步代码块1
1 public class MyObject { 2 private static MyObject myObject ; 3 private MyObject(){} 4 5 public static MyObject getInstance(){ 6 try { 7 synchronized (MyObject.class) { 8 if (myObject == null) { 9 Thread.sleep(1000); 10 myObject = new MyObject(); 11 } 12 } 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 return myObject; 17 } 18 19 public static void main(String[] args) { 20 21 for (int i = 0;i < 5;i++) { 22 Thread t = new Thread(new Runnable() { 23 @Override 24 public void run() { 25 System.out.println(MyObject.getInstance().hashCode()); 26 } 27 }); 28 t.start(); 29 } 30 } 31 }
------------------------------------------------------console------------------------------------------------------
1883420913 1883420913 1883420913 1883420913 1883420913
和synchronized声明方法的方式一样都是同步运行的,效率很低
解决方案3:同步代码块,同步部分重要代码
1 public class MyObject { 2 private static MyObject myObject ; 3 private MyObject(){} 4 5 public static MyObject getInstance(){ 6 try { 7 if (myObject == null) { 8 Thread.sleep(1000); 9 //有非线程安全问题 10 synchronized (MyObject.class) { 11 myObject = new MyObject(); 12 } 13 } 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 return myObject; 18 } 19 20 public static void main(String[] args) { 21 22 for (int i = 0;i < 5;i++) { 23 Thread t = new Thread(new Runnable() { 24 @Override 25 public void run() { 26 System.out.println(MyObject.getInstance().hashCode()); 27 } 28 }); 29 t.start(); 30 } 31 } 32 }
------------------------------------------------------console------------------------------------------------------
100758745 1460486463 635845204 161899259 1833680959
效率得到了提高,但是会出现非线程安全的问题
解决方案4:DCL双锁检查
1 public class MyObject { 2 private static MyObject myObject ; 3 private MyObject(){} 4 5 public static MyObject getInstance(){ 6 try { 7 if (myObject == null) { 8 Thread.sleep(1000); 9 //有非线程安全问题 10 synchronized (MyObject.class) { 11 if(myObject == null) { 12 myObject = new MyObject(); 13 } 14 } 15 } 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 return myObject; 20 } 21 22 public static void main(String[] args) { 23 24 for (int i = 0;i < 5;i++) { 25 Thread t = new Thread(new Runnable() { 26 @Override 27 public void run() { 28 System.out.println(MyObject.getInstance().hashCode()); 29 } 30 }); 31 t.start(); 32 } 33 } 34 }
------------------------------------------------------console------------------------------------------------------
1833680959 1833680959 1833680959 1833680959 1833680959
成功解决”懒汉模式“遇到多线程的问题,常用方案
静态内置类实现单例模式
1) 如何保证线程安全: 因为内部的静态类只会被加载一次,只会有一个实例对象,所以是线程安全的
2) 内部类的加载机制: java中的内部类是延时加载的,只有在第一次使用时加载;不使用就不加载
1 public class MyObject { 2 private static class MyObjectHandler{ 3 private static MyObject myObject = new MyObject(); 4 } 5 private MyObject(){} 6 7 public static MyObject getInstance(){ 8 return MyObjectHandler.myObject; 9 } 10 11 public static void main(String[] args) { 12 for (int i = 0;i < 5;i++) { 13 Thread t = new Thread(new Runnable() { 14 @Override 15 public void run() { 16 System.out.println(com.qf.test02.MyObject.getInstance().hashCode()); 17 } 18 }); 19 t.start(); 20 } 21 } 22 }
------------------------------------------------------console------------------------------------------------------
1654248691 1654248691 1654248691 1654248691 1654248691
序列化与反序列化实现
反序列化中反射会破坏单例模式:
一般来说, 一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来,与以前序列化的对象不能equlas
静态内部类可以实现线程安全,但是如果遇到序列化对象时,使用默认方式运行的结果还是多例的
多例环境复现:
1 public class MyObject implements Serializable { 2 private static class MyObjectHandler{ 3 private static MyObject myObject = new MyObject(); 4 } 5 private MyObject(){} 6 7 public static MyObject getInstance(){ 8 return MyObjectHandler.myObject; 9 } 10 11 public static void main(String[] args) { 12 try { 13 MyObject myObject = getInstance(); 14 FileOutputStream fos = new FileOutputStream(new File("test.txt")); 15 ObjectOutputStream oos = new ObjectOutputStream(fos); 16 oos.writeObject(myObject); 17 oos.close(); 18 fos.close(); 19 System.out.println(myObject.hashCode()); 20 } catch (FileNotFoundException e) { 21 e.printStackTrace(); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 26 try { 27 FileInputStream fis = new FileInputStream(new File("test.txt")); 28 ObjectInputStream ois = new ObjectInputStream(fis); 29 MyObject mm = (MyObject) ois.readObject(); 30 ois.close(); 31 fis.close(); 32 System.out.println(mm.hashCode()); 33 } catch (FileNotFoundException e) { 34 e.printStackTrace(); 35 } catch (IOException e) { 36 e.printStackTrace(); 37 } catch (ClassNotFoundException e) { 38 e.printStackTrace(); 39 } 40 } 41 }
------------------------------------------------------console------------------------------------------------------
21685669 1480010240
解决方案:反序列化时使用readResolve()方法
序列化操作提供了一个很特别的钩子(hook):类中具有一个被实例化的方法readresolve(),这个方法可以确保类的开发人员在序列化将会返回怎样的object上具有发言权。足够奇怪的,readresolve()并不是静态的,但是在序列化创建实例的时候被引用
1 public class MyObject implements Serializable { 2 private static class MyObjectHandler{ 3 private static MyObject myObject = new MyObject(); 4 } 5 private MyObject(){} 6 7 public static MyObject getInstance(){ 8 return MyObjectHandler.myObject; 9 } 10 11 protected Object readResolve(){ 12 System.out.println("调用了readResolve方法"); 13 return MyObjectHandler.myObject; 14 } 15 16 public static void main(String[] args) { 17 try { 18 MyObject myObject = getInstance(); 19 FileOutputStream fos = new FileOutputStream(new File("test.txt")); 20 ObjectOutputStream oos = new ObjectOutputStream(fos); 21 oos.writeObject(myObject); 22 oos.close(); 23 fos.close(); 24 System.out.println(myObject.hashCode()); 25 } catch (FileNotFoundException e) { 26 e.printStackTrace(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 31 try { 32 FileInputStream fis = new FileInputStream(new File("test.txt")); 33 ObjectInputStream ois = new ObjectInputStream(fis); 34 MyObject mm = (MyObject) ois.readObject(); 35 ois.close(); 36 fis.close(); 37 System.out.println(mm.hashCode()); 38 } catch (FileNotFoundException e) { 39 e.printStackTrace(); 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } catch (ClassNotFoundException e) { 43 e.printStackTrace(); 44 } 45 } 46 }
------------------------------------------------------console------------------------------------------------------
21685669
调用了readResolve方法
21685669
static代码块实现单例模式
1 public class MyObject { 2 private static MyObject myObject = null; 3 private MyObject(){} 4 static { 5 myObject = new MyObject(); 6 } 7 public static MyObject getInstance(){ 8 return myObject; 9 } 10 11 public static void main(String[] args) { 12 Runnable runnable = new Runnable() { 13 @Override 14 public void run() { 15 for (int i = 0; i < 5; i++) { 16 System.out.println(MyObject.getInstance().hashCode()); 17 } 18 } 19 }; 20 21 Thread t1 = new Thread(runnable); 22 Thread t2 = new Thread(runnable); 23 Thread t3 = new Thread(runnable); 24 25 t1.start(); 26 t2.start(); 27 t3.start(); 28 } 29 }
------------------------------------------------------console------------------------------------------------------
161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259 161899259
enum枚举数据类型实现单例模式
枚举和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用
1 public class MyObject { 2 private MyObject(){} 3 public static MyObject getInstance(){ 4 return EnumMyObject.INSTANCE.getInstance(); 5 } 6 enum EnumMyObject{ 7 INSTANCE; 8 private MyObject myObject; 9 private EnumMyObject(){ 10 myObject = new MyObject(); 11 } 12 13 public MyObject getInstance(){ 14 return myObject; 15 } 16 } 17 18 public static void main(String[] args) { 19 Runnable runnable = new Runnable() { 20 @Override 21 public void run() { 22 System.out.println(MyObject.getInstance().hashCode()); 23 } 24 }; 25 int i = 0; 26 while (i<3) { 27 Thread t1 = new Thread(runnable); 28 Thread t2 = new Thread(runnable); 29 t1.start(); 30 t2.start(); 31 i++; 32 } 33 } 34 }
------------------------------------------------------console------------------------------------------------------
2075012821 2075012821 2075012821 2075012821 2075012821 2075012821