Spring三 多线程下的单例

(3)、 针对某些重要的代码来进行单独的同步(可能非线程安全)

针对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率,我们来看一下:

 

[java] view plain copy
 
  1. package org.mlinge.s04;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = null;  
  6.       
  7.     private MySingleton(){}  
  8.        
  9.     public static MySingleton getInstance() {  
  10.         try {    
  11.             if(instance != null){//懒汉式   
  12.                   
  13.             }else{  
  14.                 //创建实例之前可能会有一些准备性的耗时工作   
  15.                 Thread.sleep(300);  
  16.                 synchronized (MySingleton.class) {  
  17.                     instance = new MySingleton();  
  18.                 }  
  19.             }   
  20.         } catch (InterruptedException e) {   
  21.             e.printStackTrace();  
  22.         }  
  23.         return instance;  
  24.     }  
  25. }  

此时同样使用前面验证多线程下执行情况的MyThread类来进行验证,将其放入到org.mlinge.s04包下运行,执行结果如下:

[plain] view plain copy
 
  1. 1481297610  
  2. 397630378  
  3. 1863264879  
  4. 1210420568  
  5. 1935123450  
  6. 369539795  
  7. 590202901  
  8. 1718900954  
  9. 1689058373  
  10. 602269801  

从运行结果来看,这样的方法进行代码块同步,代码的运行效率是能够得到提升,但是却没能保住线程的安全性。看来还得进一步考虑如何解决此问题。

 

(4)、 Double Check Locking 双检查锁机制(推荐)

为了达到线程安全,又能提高代码执行效率,我们这里可以采用DCL的双检查锁机制来完成,代码实现如下:

 

[java] view plain copy
 
  1. package org.mlinge.s05;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     //使用volatile关键字保其可见性  
  6.     volatile private static MySingleton instance = null;  
  7.       
  8.     private MySingleton(){}  
  9.        
  10.     public static MySingleton getInstance() {  
  11.         try {    
  12.             if(instance != null){//懒汉式   
  13.                   
  14.             }else{  
  15.                 //创建实例之前可能会有一些准备性的耗时工作   
  16.                 Thread.sleep(300);  
  17.                 synchronized (MySingleton.class) {  
  18.                     if(instance == null){//二次检查  
  19.                         instance = new MySingleton();  
  20.                     }  
  21.                 }  
  22.             }   
  23.         } catch (InterruptedException e) {   
  24.             e.printStackTrace();  
  25.         }  
  26.         return instance;  
  27.     }  
  28. }  

将前面验证多线程下执行情况的MyThread类放入到org.mlinge.s05包下运行,执行结果如下:

[java] view plain copy
 
  1. 369539795  
  2. 369539795  
  3. 369539795  
  4. 369539795  
  5. 369539795  
  6. 369539795  
  7. 369539795  
  8. 369539795  
  9. 369539795  
  10. 369539795  

从运行结果来看,该中方法保证了多线程并发下的线程安全性。

 

这里在声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性。

4、使用静态内置类实现单例模式

DCL解决了多线程并发下的线程安全问题,其实使用其他方式也可以达到同样的效果,代码实现如下:

 

[java] view plain copy
 
  1. package org.mlinge.s06;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     //内部类  
  6.     private static class MySingletonHandler{  
  7.         private static MySingleton instance = new MySingleton();  
  8.     }   
  9.       
  10.     private MySingleton(){}  
  11.        
  12.     public static MySingleton getInstance() {   
  13.         return MySingletonHandler.instance;  
  14.     }  
  15. }  

以上代码就是使用静态内置类实现了单例模式,这里将前面验证多线程下执行情况的MyThread类放入到org.mlinge.s06包下运行,执行结果如下:

[java] view plain copy
 
  1. 1718900954  
  2. 1718900954  
  3. 1718900954  
  4. 1718900954  
  5. 1718900954  
  6. 1718900954  
  7. 1718900954  
  8. 1718900954  
  9. 1718900954  
  10. 1718900954  

从运行结果来看,静态内部类实现的单例在多线程并发下单个实例得到了保证。

 

5、序列化与反序列化的单例模式实现

静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。

代码实现如下:

 

[java] view plain copy
 
  1. package org.mlinge.s07;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class MySingleton implements Serializable {  
  6.        
  7.     private static final long serialVersionUID = 1L;  
  8.   
  9.     //内部类  
  10.     private static class MySingletonHandler{  
  11.         private static MySingleton instance = new MySingleton();  
  12.     }   
  13.       
  14.     private MySingleton(){}  
  15.        
  16.     public static MySingleton getInstance() {   
  17.         return MySingletonHandler.instance;  
  18.     }  
  19. }  

序列化与反序列化测试代码:

 

 

[java] view plain copy
 
  1. package org.mlinge.s07;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.FileOutputStream;  
  7. import java.io.IOException;  
  8. import java.io.ObjectInputStream;  
  9. import java.io.ObjectOutputStream;  
  10.   
  11. public class SaveAndReadForSingleton {  
  12.       
  13.     public static void main(String[] args) {  
  14.         MySingleton singleton = MySingleton.getInstance();  
  15.           
  16.         File file = new File("MySingleton.txt");  
  17.           
  18.         try {  
  19.             FileOutputStream fos = new FileOutputStream(file);  
  20.             ObjectOutputStream oos = new ObjectOutputStream(fos);  
  21.             oos.writeObject(singleton);  
  22.             fos.close();  
  23.             oos.close();  
  24.             System.out.println(singleton.hashCode());  
  25.         } catch (FileNotFoundException e) {   
  26.             e.printStackTrace();  
  27.         } catch (IOException e) {   
  28.             e.printStackTrace();  
  29.         }  
  30.           
  31.         try {  
  32.             FileInputStream fis = new FileInputStream(file);  
  33.             ObjectInputStream ois = new ObjectInputStream(fis);  
  34.             MySingleton rSingleton = (MySingleton) ois.readObject();  
  35.             fis.close();  
  36.             ois.close();  
  37.             System.out.println(rSingleton.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.     }  
  47. }  

运行以上代码,得到的结果如下:

 

 

[sql] view plain copy
 
  1. 865113938  
  2. 1442407170  

从结果中我们发现,序列号对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。那怎么来解决这一问题呢?

 

解决办法就是在反序列化的过程中使用readResolve()方法,单例实现的代码如下:

 

[java] view plain copy
 
  1. package org.mlinge.s07;  
  2.   
  3. import java.io.ObjectStreamException;  
  4. import java.io.Serializable;  
  5.   
  6. public class MySingleton implements Serializable {  
  7.        
  8.     private static final long serialVersionUID = 1L;  
  9.   
  10.     //内部类  
  11.     private static class MySingletonHandler{  
  12.         private static MySingleton instance = new MySingleton();  
  13.     }   
  14.       
  15.     private MySingleton(){}  
  16.        
  17.     public static MySingleton getInstance() {   
  18.         return MySingletonHandler.instance;  
  19.     }  
  20.       
  21.     //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉  
  22.     protected Object readResolve() throws ObjectStreamException {  
  23.         System.out.println("调用了readResolve方法!");  
  24.         return MySingletonHandler.instance;   
  25.     }  
  26. }  

再次运行上面的测试代码,得到的结果如下:

 

 

[plain] view plain copy
 
  1. 865113938  
  2. 调用了readResolve方法!  
  3. 865113938  

从运行结果可知,添加readResolve方法后反序列化后得到的实例和序列化前的是同一个实例,单个实例得到了保证。

 

6、使用static代码块实现单例

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。

 

[java] view plain copy
 
  1. package org.mlinge.s08;  
  2.   
  3. public class MySingleton{  
  4.        
  5.     private static MySingleton instance = null;  
  6.        
  7.     private MySingleton(){}  
  8.   
  9.     static{  
  10.         instance = new MySingleton();  
  11.     }  
  12.       
  13.     public static MySingleton getInstance() {   
  14.         return instance;  
  15.     }   
  16. }  

测试代码如下:

 

 

[java] view plain copy
 
  1. package org.mlinge.s08;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         for (int i = 0; i < 5; i++) {  
  8.             System.out.println(MySingleton.getInstance().hashCode());  
  9.         }  
  10.     }  
  11.       
  12.     public static void main(String[] args) {   
  13.           
  14.         MyThread[] mts = new MyThread[3];  
  15.         for(int i = 0 ; i < mts.length ; i++){  
  16.             mts[i] = new MyThread();  
  17.         }  
  18.           
  19.         for (int j = 0; j < mts.length; j++) {  
  20.             mts[j].start();  
  21.         }  
  22.     }  
  23. }  

运行结果如下:

 

 

[java] view plain copy
 
  1. 1718900954  
  2. 1718900954  
  3. 1718900954  
  4. 1718900954  
  5. 1718900954  
  6. 1718900954  
  7. 1718900954  
  8. 1718900954  
  9. 1718900954  
  10. 1718900954  
  11. 1718900954  
  12. 1718900954  
  13. 1718900954  
  14. 1718900954  
  15. 1718900954  

从运行结果看,单例的线程安全性得到了保证。

 

7、使用枚举数据类型实现单例模式

枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例:

 

[java] view plain copy
 
  1. package org.mlinge.s09;  
  2.   
  3. public enum EnumFactory{   
  4.       
  5.     singletonFactory;  
  6.       
  7.     private MySingleton instance;  
  8.       
  9.     private EnumFactory(){//枚举类的构造方法在类加载是被实例化  
  10.         instance = new MySingleton();  
  11.     }  
  12.           
  13.     public MySingleton getInstance(){  
  14.         return instance;  
  15.     }  
  16.       
  17. }  
  18.   
  19. class MySingleton{//需要获实现单例的类,比如数据库连接Connection  
  20.     public MySingleton(){}   
  21. }  

测试代码如下:

 

 

[java] view plain copy
 
  1. package org.mlinge.s09;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         System.out.println(EnumFactory.singletonFactory.getInstance().hashCode());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {   
  11.           
  12.         MyThread[] mts = new MyThread[10];  
  13.         for(int i = 0 ; i < mts.length ; i++){  
  14.             mts[i] = new MyThread();  
  15.         }  
  16.           
  17.         for (int j = 0; j < mts.length; j++) {  
  18.             mts[j].start();  
  19.         }  
  20.     }  
  21. }  

执行后得到的结果:

[java] view plain copy
 
  1. 1481297610  
  2. 1481297610  
  3. 1481297610  
  4. 1481297610  
  5. 1481297610  
  6. 1481297610  
  7. 1481297610  
  8. 1481297610  
  9. 1481297610  
  10. 1481297610  

运行结果表明单例得到了保证,但是这样写枚举类被完全暴露了,据说违反了“职责单一原则”,那我们来看看怎么进行改造呢。

 

8、完善使用enum枚举实现单例模式

不暴露枚举类实现细节的封装代码如下:

 

[java] view plain copy
 
  1. package org.mlinge.s10;  
  2.   
  3. public class ClassFactory{   
  4.       
  5.     private enum MyEnumSingleton{  
  6.         singletonFactory;  
  7.           
  8.         private MySingleton instance;  
  9.           
  10.         private MyEnumSingleton(){//枚举类的构造方法在类加载是被实例化  
  11.             instance = new MySingleton();  
  12.         }  
  13.    
  14.         public MySingleton getInstance(){  
  15.             return instance;  
  16.         }  
  17.     }   
  18.    
  19.     public static MySingleton getInstance(){  
  20.         return MyEnumSingleton.singletonFactory.getInstance();  
  21.     }  
  22. }  
  23.   
  24. class MySingleton{//需要获实现单例的类,比如数据库连接Connection  
  25.     public MySingleton(){}   
  26. }  

验证单例实现的代码如下:

 

 

[java] view plain copy
 
  1. package org.mlinge.s10;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         System.out.println(ClassFactory.getInstance().hashCode());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {   
  11.           
  12.         MyThread[] mts = new MyThread[10];  
  13.         for(int i = 0 ; i < mts.length ; i++){  
  14.             mts[i] = new MyThread();  
  15.         }  
  16.           
  17.         for (int j = 0; j < mts.length; j++) {  
  18.             mts[j].start();  
  19.         }  
  20.     }  
  21. }  

验证结果:

 

 

[java] view plain copy
 
    1. 1935123450  
    2. 1935123450  
    3. 1935123450  
    4. 1935123450  
    5. 1935123450  
    6. 1935123450  
    7. 1935123450  
    8. 1935123450  
    9. 1935123450  
    10. 1935123450  
posted @ 2018-03-06 08:40  Gtr_Right  阅读(195)  评论(0编辑  收藏  举报