单例模式没你想的那么简单

网上到处都是懒汉,饿汉模式。给两个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()时候实例化了一次,只不过新创建的对象没有被返回而已。

 

posted @ 2020-04-17 21:51  思思博士  阅读(258)  评论(0编辑  收藏  举报