zoukankan      html  css  js  c++  java
  • 单例模式没你想的那么简单

    网上到处都是懒汉,饿汉模式。给两个Demo就算过去了吧。

    饿汉单例模式:在类加载的时候,就开始实例化了。

    public class HungrySingleton {
    
        private static  HungrySingleton one=new HungrySingleton();
    
        private HungrySingleton(){}
    
        public  static HungrySingleton getInstance(){
            return one;
        }
    
        public static void main(String[] args) {
            HungrySingleton one1=HungrySingleton.getInstance();
            HungrySingleton one2=HungrySingleton.getInstance();
            System.out.println(one1==one2);
        }
    }

    懒汉模式:在第一次获取实例化对象的时候,开始实例化。

    public class LazySingleton {
        private static LazySingleton one=null;
    
        private LazySingleton() {
        }
        public  static LazySingleton getInstance(){
            if(one==null){
                one=new LazySingleton();
            }
            return one;
        }
    
        public static void main(String[] args) {
            LazySingleton one1=LazySingleton.getInstance();
            LazySingleton one2=LazySingleton.getInstance();
            System.out.println(one1 == one2);
        }
    }

    无论何种模式先要把构造函数给私有化,否则就变成了“勤快汉”模式了;这名字是我瞎编的。

    饿汉模式典型用法:Spring中IOC容器ApplictionContext,连接池.

    懒汉模式典型用法:不知道啊。

    饿汉模式没缺点,最多也就在没使用的时候,分配个内存空间。

    下面着重说说懒汉模式,所以来个分割线吧。

    =================================

    懒汉单例模式的线程安全

    上面的懒汉模式有线程安全问题,就是多个线程在同时执行的时候,怎么保证LazySingleton只被实例化了一次。

    线程类:

    public class ExectorThread implements Runnable {
        @Override
        public void run() {
            LazySimpleSingleton one=LazySimpleSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + one);
        }
    }

    单例类:

    public class LazySimpleSingleton {
    
        private static LazySimpleSingleton one=null;
    
        private LazySimpleSingleton() {
        }
        public static LazySimpleSingleton getInstance(){
            if(one==null){
                one= new LazySimpleSingleton();
            }
            return one;
        }
    }

    测试类:

    public class LazyTest {
        public static void main(String[] args) {
            Thread t1=new Thread(new ExectorThread());
            Thread t2=new Thread(new ExectorThread());
    
            t1.start();
            t2.start();
    
            System.out.println("end");
        }
    }

    第一个线程把one实例化完成之后,还没有来得及刷新到内存,第二个线程就把one读入内存,又进行了一次实例化。

    最简单的办法就是给实例化方法getInstance()添加一个synchronized.

    修改后代码如下

    public class LazySimpleSingleton {
    
        private static LazySimpleSingleton one=null;
    
        private LazySimpleSingleton() {
        }
        public static synchronized LazySimpleSingleton getInstance(){
            if(one==null){
                one= new LazySimpleSingleton();
            }
            return one;
        }
    }

    这种模式有一个性能问题;比如100个线程在同时调用getInstance()的时候,99个全部都阻塞在这个位置了,

    包括one已经不是空值的时候,依然在阻塞中;改造上面的代码,让已经实例化之后的线程不在阻塞。

     1 public class LazySimpleSingleton {
     2 
     3     private static LazySimpleSingleton one=null;
     4 
     5     private LazySimpleSingleton() {
     6     }
     7     public static  LazySimpleSingleton getInstance(){
     8         //索前判断是否实例化了,实例化了就不用进入synchronized中了
     9         if(one==null){
    10             synchronized(LazySimpleSingleton.class){
    11                 //上面one==null了,不代表此时还是null
    12                 if(one==null){
    13                     one= new LazySimpleSingleton();
    14                 }
    15                 return one;
    16             }
    17         }
    18         return one;
    19     }
    20 }

    反射破坏单例

    以饿汉单例的Demo为例子进行改造。

     1 public class HungrySingleton {
     2     
     3     private static  HungrySingleton one=new HungrySingleton();
     4 
     5     private HungrySingleton(){}
     6 
     7     public  static HungrySingleton getInstance(){
     8         return one;
     9     }
    10 
    11     public static void main(String[] args)  throws Exception{
    12         HungrySingleton one1=HungrySingleton.getInstance();
    13         Constructor constructor=HungrySingleton.class.getDeclaredConstructor(null);
    14         //强制访问构造器,包括私有成员
    15         constructor.setAccessible(true);
    16         HungrySingleton one2=(HungrySingleton) constructor.newInstance();
    17         System.out.println(one1==one2);
    18     }
    19 }

    打印结果显示false.说明被实例化了两次;修改代码如下。

    public class HungrySingleton {
    
        private static  HungrySingleton one=new HungrySingleton();
    
        private HungrySingleton(){
            if(one!=null){
                throw new RuntimeException("已经实例化过了,本次实例化失败");
            }
        }
    
        public  static HungrySingleton getInstance(){
            return one;
        }
    
        public static void main(String[] args)  throws Exception{
            HungrySingleton one1=HungrySingleton.getInstance();
            Constructor constructor=HungrySingleton.class.getDeclaredConstructor(null);
            //强制访问构造器,包括私有成员
            constructor.setAccessible(true);
            HungrySingleton one2=(HungrySingleton) constructor.newInstance();
            System.out.println(one1==one2);
        }
    }

    打印结果:

    序列化破坏单例模式 

    以饿汉模式为例:

    public class SeriableSingleton  implements Serializable {
        public final  static SeriableSingleton one=new SeriableSingleton();
    
        private SeriableSingleton() {
        }
    
        public static SeriableSingleton getInstance(){
            return one;
        }  
    }

    测试类:

     1 public class SeriableSingletonTest {
     2     public static void main(String[] args) {
     3         SeriableSingleton s1=null;
     4         SeriableSingleton s2=SeriableSingleton.getInstance();
     5 
     6         FileOutputStream fos=null;
     7         try {
     8             fos=new FileOutputStream("one.obj");
     9             ObjectOutputStream oos=new ObjectOutputStream(fos);
    10             oos.writeObject(s2);
    11             oos.flush();
    12             oos.close();
    13 
    14             FileInputStream fis=new FileInputStream("one.obj");
    15             ObjectInputStream ois=new ObjectInputStream(fis);
    16             s1=(SeriableSingleton) ois.readObject();
    17             ois.close();
    18 
    19             System.out.println(s1 == s2);
    20 
    21         } catch (Exception e){
    22 
    23         }
    24     }
    25 }

    显示结果是false.

    这个问题很好办,加一行代码的事情。

     1 public class SeriableSingleton  implements Serializable {
     2     public final  static SeriableSingleton one=new SeriableSingleton();
     3 
     4     private SeriableSingleton() {
     5     }
     6 
     7     public static SeriableSingleton getInstance(){
     8         return one;
     9     }
    10 
    11     private Object readResolve(){
    12         return one;
    13     }
    14 }

    上面红色就是添加的一个方法。

    这是一个奇怪的现象,直接从源码中找答案吧。

    readObject()源码

    readObject0()源码 

    private Object readObject0(boolean unshared) throws IOException {
           
    ...
                    case TC_OBJECT:
                        return checkResolve(readOrdinaryObject(unshared));
    ........
                    
        }

    上面代码去除了一些无用代码。

    继续看readOrdinaryObject()源码

    private Object readOrdinaryObject(boolean unshared)
            throws IOException
        {   
          
          //判断是否有readResolve()方法
            if (obj != null &&
                handles.lookupException(passHandle) == null &&
                desc.hasReadResolveMethod())
            {        
                Object rep = desc.invokeReadResolve(obj);
               
            }
    
            return obj;
        }
    invokeReadResolve()源码
     Object invokeReadResolve(Object obj)
            throws IOException, UnsupportedOperationException
        {
           ..........
            if (readResolveMethod != null) {
                try {//利用反射调用readResolve()方法
                    return readResolveMethod.invoke(obj, (Object[]) null);
                }
            } 
        }

    因为JDK在readObject()时候,判断了有没有readResolve()方法,如果有的话就执行这个方法,没有就不执行了,我们充分利用了这个特点,给他直接返回了一个one对象;

    所以就不执行实例化了。其实这个地方在readObject()时候实例化了一次,只不过新创建的对象没有被返回而已。

  • 相关阅读:
    3.28
    03.21
    03.16
    03.15
    03.14小记
    [LC] 96. Unique Binary Search Trees
    [LC] 298. Binary Tree Longest Consecutive Sequence
    [LC] 102. Binary Tree Level Order Traversal
    [LC] 107. Binary Tree Level Order Traversal II
    [LC] 513. Find Bottom Left Tree Value
  • 原文地址:https://www.cnblogs.com/guoyansi19900907/p/12721676.html
Copyright © 2011-2022 走看看