zoukankan      html  css  js  c++  java
  • java单例模式实现

    1.最基本的单例模式

    /**
     * @author LearnAndGet
     * @time 2018年11月13日
     *  最基本的单例模式
     */
    public class SingletonV1 {
        
        private static SingletonV1 instance = new SingletonV1();;
        
        //构造函数私有化
        private SingletonV1() {}
    
        public static SingletonV1 getInstance() 
        {
            return instance;
        }
    }
    import org.junit.Test;
    public class SingletonTest {
        
        @Test
        public void test01() throws Exception
        {
            SingletonV1 s1 = SingletonV1.getInstance();
            SingletonV1 s2 = SingletonV1.getInstance();
            System.out.println(s1.hashCode());
            System.out.println(s2.hashCode());
        }
    }
    
    //运行结果如下:
    589873731
    589873731

    2.类加载时不初始化实例的模式

      上述单例模式在类加载的时候,就会生成实例,可能造成空间浪费,如果需要修改成,在需要使用时才生成实例,则可修改代码如下:

     1 public class SingletonV2 {
     2     
     3     private static SingletonV2 instance;
     4     
     5     //构造函数私有化
     6     private SingletonV2() {}
     7 
     8     public static SingletonV2 getInstance(){
    10         if(instance == null) 
    11         {
    12             instance = new SingletonV2();
    13         }
    14         return instance;
    15     }
    16 }

    然而,上述方案虽然在类加载时不会生成实例,但是存在线程安全问题,如果线程A在执行到第10行时,线程B也进入该代码块,恰好也执行好第10行,此时如果实例尚未生成,则线程A和线程B都会执行第12行的代码,各自生成一个实例,此时就违背了单例模式的设计原则。实际测试代码如下:

    public class SingletonTest {
    
        @Test
        public void test02() throws Exception
        {    
            for(int i=0;i<1000;i++) 
            {
                Thread th1 = new getInstanceThread();
                th1.start();
            }
        
        }
        
        class getInstanceThread extends Thread
        {
            public void run() 
            {
                try 
                {
                    SingletonV2 s = SingletonV2.getInstance();
                    System.out.println(Thread.currentThread().getName()+" get Instance "+s.hashCode()+" Time: "+System.currentTimeMillis());
                }catch(Exception e) 
                {
                    e.printStackTrace();
                }
            }
        }
        
    }

    经过多次测试,可能产生如下输出结果:

      

    3.线程安全的单例模式

      在上述单例模式下进行改进,在getInstance方法前加入 Sychronized关键字,来实现线程安全,修改后代码如下:

     1 public class SingletonV3 {
     2     
     3     private static SingletonV3 instance;
     4     
     5     //构造函数私有化
     6     private SingletonV3() {}
     7 
        //synchronized关键字在静态方法上,锁定的是当前类:sychronized关键字 8 public static synchronized SingletonV3 getInstance() 9 { 10 if(instance == null) 11 { 12 instance = new SingletonV3(); 13 } 14 return instance; 15 } 16 }

     增加sychronized关键字后,确实能够改善线程安全问题,但是也带来了额外的锁开销。性能受到一定影响。举例来说,此时如果有1000个线程都需要使用SingletonV3实例,因为加锁的位置在getInstance上,因此,每个线程都必须等待其他获取了锁的线程完全执行完锁中的方法后,才能够进入该方法并获取自己的实例。

    4.双重校检+线程安全单例模式

      于是可以在上述代码的基础上,只有当Singleton实例未被初始化时,对实例化方法加锁即可。在Singleton实例已经被初始化时,无需加锁,直接返回当前Singleton对象。代码如下:

     1     private static SingletonV4 instance;
     2     
     3     //构造函数私有化
     4     private SingletonV4() {}
     5 
     6     public static SingletonV4 getInstance()
     7     {
     8         if(instance == null) 
     9         {
    10             synchronized(SingletonV4.class) 
    11             {
    12                 //双重校检
    13                 if(instance == null) 
    14                 {
    15                     instance = new SingletonV4();
    16                 }
    17             }
    18         }
    19         return instance;
    20     }

    5.内部类单例模式

      尽管上述方案解决了同步问题,双重校检也使得性能开销大大减小,但是,只有有synchronized关键字的存在。性能多多少少还是会有一些影响,此时,我们想到了 "内部类"的用法。

      ①.内部类不会随着类的加载而加载

      ②.一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

      静态内部类随着方法调用而被加载,只加载一次,不存在并发问题,所以是线程安全。基于此,修改代码如下:

      

     /推荐指数:★★★★★
    
    
     1 public class SingletonV5 {
     2     //构造函数私有化
     3     private SingletonV5() {}
     4 
     5     static class SingetonGet
     6     {
     7         private static final SingletonV5 instance = new SingletonV5();
     8     }
     9     
    10     public static SingletonV5 getInstance() 
    11     {
    12         return SingetonGet.instance;
    13     }
    14 }

    6.反射都不能破坏的单例模式

      静态内部类实现的单例模式,是目前比较推荐的方式,但是在java功能强大反射的机制下,它就是个弟弟,此时利用反射仍然能够创建出多个实例,以下是创建实例的代码:

      

     1     @Test
     2     public void test4()
     3     {    
     4         //普通方式获取实例s1,s2
     5         SingletonV5 s1 = SingletonV5.getInstance();
     6         SingletonV5 s2 = SingletonV5.getInstance();
     7         //利用反射获取实例s3,s4
     8         SingletonV5 s3 = null;
     9         SingletonV5 s4 = null;
    10         try 
    11         {
    12             Class<SingletonV5> clazz = SingletonV5.class;
    13             Constructor<SingletonV5> constructor = clazz.getDeclaredConstructor();
    14             constructor.setAccessible(true);
    15             s3 = constructor.newInstance();
    16             s4 = constructor.newInstance();
    17         }catch(Exception e) 
    18         {
    19             e.printStackTrace();
    20         }
    21         
    22         System.out.println(s1.hashCode());
    23         System.out.println(s2.hashCode());
    24         System.out.println(s3.hashCode());
    25         System.out.println(s4.hashCode());    
    26     }

    输出结果如下:

      

    589873731
    589873731
    200006406
    2052001577

     可以看到,s1和s2拥有相同的哈希码,因此他们是同一个实例,但是s3、s4,是通过反射后用构造函数重新构造生成的实例,他们均与s1,s2不同。此时单例模式下产生了多个不同的对象,违反了设计原则。

    基于上述反射可能造成的单例模式失效,考虑在私有的构造函数中添加是否初始化的标记位,使私有构造方法只可能被执行一次。

    public class SingletonV6 {
        //是否已经初始化过的标记位
        private static boolean isInitialized = false;
        
        //构造函数中,当实例已经被初始化时,不能继续获取新实例
        private SingletonV6() 
        {
            synchronized(SingletonV6.class) 
            {
                if(isInitialized == false) 
                {
                    isInitialized = !isInitialized;
                }else 
                {
                    throw new RuntimeException("单例模式被破坏...");
                }
            }    
        }
    
        static class SingetonGet
        {
            private static final SingletonV6 instance = new SingletonV6();
        }
        
        public static SingletonV6 getInstance() 
        {
            return SingetonGet.instance;
        }
    }

    测试代码如下:

        @Test
        public void test5()
        {    
            SingletonV6 s1 = SingletonV6.getInstance();
            SingletonV6 s2 = null;
            try 
            {
                Class<SingletonV6> clazz = SingletonV6.class;
                Constructor<SingletonV6> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);
                s2 = constructor.newInstance();
    
            }catch(Exception e) 
            {
                e.printStackTrace();
            }    
            System.out.println(s1.hashCode());
            System.out.println(s2.hashCode());
        }

      运行上述代码时,会抛出异常:

      

    java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at SingletonTest.SingletonTest.test5(SingletonTest.java:98)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
    Caused by: java.lang.RuntimeException: 单例模式被破坏...
        at SingletonTest.SingletonV6.<init>(SingletonV6.java:26)
        ... 28 more
    2052001577

    7.序列化反序列化都不能破坏的单例模式

      经过上述改进,反射也不能够破坏单例模式了。但是,依然存在一种可能造成上述单例模式产生两个不同的实例,那就是序列化。当一个对象A经过序列化,然后再反序列化,获取到的对象B和A是否是同一个实例呢,验证代码如下:

      

    /**
     * @Author {LearnAndGet}
     * @Time 2018年11月13日
     * @Discription:测试序列化并反序列化是否还是同一对象
     */
    package SingletonTest;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    
    public class Main {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            SingletonV6 s1 = SingletonV6.getInstance();
            
            ObjectOutput objOut = null;
            
            try {
                //将s1序列化(记得将Singleton实现Serializable接口)
                objOut = new ObjectOutputStream(new FileOutputStream("c:\a.objFile"));
                objOut.writeObject(s1);
                objOut.close();
                
                //反序列化得到s2
                ObjectInput objIn = new ObjectInputStream(new FileInputStream("c:\a.objFile"));
                SingletonV6 s2 = (SingletonV6) objIn.readObject();
                objIn.close();
                
                System.out.println(s1.hashCode());
                System.out.println(s2.hashCode());
                
                } catch (Exception e) 
                {
                // TODO Auto-generated catch block
                e.printStackTrace();
                }
        }
    
    }

      输出结果如下:

      

    1118140819
    990368553

     可见,此时序列化前的对象s1和经过序列化->反序列化步骤后的到的对象s2,并不是同一个对象,因此,出现了两个实例,再次违背了单例模式的设计原则。

    为了消除问题,在单例模式类中,实现Serializable接口之后 添加对readResolve()方法的实现:当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。这就确保了在序列化和反序列化的过程中没人可以创建新的实例,修改后的代码如下:

      

    package SingletonTest;
    
    import java.io.Serializable;
    
    /**
     * @author LearnAndGet
     *
     * @time 2018年11月13日
     * 
     */
    public class SingletonV6  implements Serializable{
        //是否已经初始化过的标记位
        private static boolean isInitialized = false;
        
        //构造函数中,当实例已经被初始化时,不能继续获取新实例
        private SingletonV6() 
        {
            synchronized(SingletonV6.class) 
            {
                if(isInitialized == false) 
                {
                    isInitialized = !isInitialized;
                }else 
                {
                    throw new RuntimeException("单例模式被破坏...");
                }
            }    
        }
    
        static class SingetonGet
        {
            private static final SingletonV6 instance = new SingletonV6();
        }
        
        public static SingletonV6 getInstance() 
        {
            return SingetonGet.instance;
        }
        //实现readResolve方法
        private Object readResolve() 
        {
            return getInstance();
        }
    }

     重新运行上述序列化和反序列过程,可以发现,此时得到的对象是同一对象。

      

    1118140819
    1118140819

    8.总结

      在实际开发中,根据自己的需要,选择对应的单例模式即可,不一样非要实现第7节中那种无坚不摧的单例模式。毕竟不是所有场景下都需要实现序列化接口, 也并不是所有人都会用反射来破坏单例模式。因此比较常用的是第5节中的,内部类单例模式,代码简洁明了,且节省空间。

  • 相关阅读:
    Nginx 部署多个 web 项目(虚拟主机)
    Nginx 配置文件
    Linux 安装 nginx
    Linux 安装 tomcat
    Linux 安装 Mysql 5.7.23
    Linux 安装 jdk8
    Linux 安装 lrzsz,使用 rz、sz 上传下载文件
    springMVC 拦截器
    spring 事务
    基于Aspectj 注解实现 spring AOP
  • 原文地址:https://www.cnblogs.com/LearnAndGet/p/9953436.html
Copyright © 2011-2022 走看看