zoukankan      html  css  js  c++  java
  • 转载:java基础之单例

    转载:https://blog.csdn.net/goodlixueyong/article/details/51935526

    https://www.cnblogs.com/cielosun/p/6582333.html

     http://www.importnew.com/21141.html

    序列化和反序列化:https://blog.csdn.net/cselmu9/article/details/51366946

    1、单例模式

    特点:

    1)单例类只能有一个实例。
    2)单例类必须自己创建自己的唯一实例。
    3)单例类必须给所有其他对象提供这一实例。

    实现要素:1)私有静态属性 2)私有构造方法 3)公有静态工厂方法

    2、单例实现

    2.1懒汉式

    懒加载模式(使用时才实例化)。

    public class SingletonTest {
        /**
         * 懒汉式,非线程安全
         * @param args
         */
        private static SingletonTest singletonTest=null;//私有静态属性
            private SingletonTest(){}//私有构造方法
        public static SingletonTest getInstance(){//公有静态方法
            if(singletonTest==null){
                singletonTest=new SingletonTest();
            }
            return singletonTest;
        }
    }    

    存在问题:

    1)通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。

    2)线程不安全的,并发环境下很可能出现多个Singleton实例。

    测试代码:

    public class SingletonTest {
        /**
         * 懒汉式,非线程安全
         * @param args
         */
        private static SingletonTest singletonTest=null;//私有静态属性
        private SingletonTest(){}//私有构造方法
        public static SingletonTest getInstance(){//公有静态方法
            if(singletonTest==null){
                singletonTest=new SingletonTest();
            }
            return singletonTest;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(10);
            for(int i=0;i<5;i++){
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(SingletonTest.getInstance().hashCode());
                        }
                    }
                );
            }
        }
    }

    测试结果

    1711790973
    1711790973
    1711790973
    1711790973
    1711790973

    测试结果并未显示创建了多个实例对象,关于懒汉式非线程安全是否正确?

    1)网上有增加sleep方法验证其非线程安全:

    增加方法一:

    public class SingletonTest {
        /**
         * 懒汉式,非线程安全
         * @param args
         */
        private static SingletonTest singletonTest=null;//私有静态属性
        private SingletonTest(){}//私有构造方法
        public static SingletonTest getInstance(){//公有静态方法
            if(singletonTest==null){
                try {
                    Thread.sleep(100);
                    singletonTest=new SingletonTest();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return singletonTest;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(20);
            for(int i=0;i<10;i++){
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(SingletonTest.getInstance().hashCode());
                        }
                    }
                );
            }
        }
    }

    这种情况下,多线程进入阻塞状态,自然会导致创建多个实例(但生产中应该不会这么处理),个人觉得不能验证其线程安全问题。运行结果如下:

    2006466913
    425058492
    753697950
    1427465122
    1542895644
    1582065822
    1786107484
    925527685
    1716687590
    1866484817

    增加方法二:

    public class SingletonTest {
        /**
         * 懒汉式,非线程安全
         * @param args
         */
        private static SingletonTest singletonTest=null;//私有静态属性
        private SingletonTest(){}//私有构造方法
        public static SingletonTest getInstance(){//公有静态方法
            if(singletonTest==null){
                singletonTest=new SingletonTest();
            }
            return singletonTest;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(200);
            for(int i=0;i<1000;i++){//线程数10000
                executor.execute(
                    new Runnable(){
                        public void run(){
                            try {
                                Thread.sleep(300);//增加sleep方法
                                System.out.println(SingletonTest.getInstance().hashCode());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                );
            }
        }
    }

    这种情况下,和生产环境接近(获取对象前进行其他处理,但如果非sleep方式,而是普通的处理会是什么情况)。运行结果如下:

    1183896518
    1183896518
    1183896518
    1183896518
    1183896518
    1183896518
    1183896518
    1183896518
    1324694881
    1183896518
    1324694881
    1939292200

    出现了创建多个实例现象。

    2)但为什么在没有sleep方法情况下,并未出现创建多个实例问题?

    分析1:可能是线程太少,能够处理的过来。为验证,增加线程数为1000和10000。

    public class SingletonTest {
        /**
         * 懒汉式,非线程安全
         * @param args
         */
        private static SingletonTest singletonTest=null;//私有静态属性
        private SingletonTest(){}//私有构造方法
        public static SingletonTest getInstance(){//公有静态方法
            if(singletonTest==null){
                    singletonTest=new SingletonTest();
            }
            return singletonTest;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(2000);
            for(int i=0;i<10000;i++){//线程数10000
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(SingletonTest.getInstance().hashCode());
                        }
                    }
                );
            }
        }
    }

    执行结果:

    1181470557
    1181470557
    1181470557
    1181470557
    1181470557

    线程数为10000也没有创建多个对象。

    结论:待进一步了解。注意:executor并不是一个线程在跑。可能需要放到服务器处理。

     2.2饿汉式

    类加载时实例化,天生线程安全。

    public class SingletonTest {
        /**
         * 饿汉式(天生线程安全,类加载时实例化)
         * @param args
         */
        private static final SingletonTest singletonTest=new SingletonTest();//私有静态final属性
        private SingletonTest(){};
        public static SingletonTest getInstance(){
            return singletonTest;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(20);
            for(int i=0;i<100;i++){//线程数1000
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(SingletonTest.getInstance().hashCode());
                        }
                    }
                );
            }
        }

    单例的实例被声明成 static 和 final 变量。缺点是:不是一种懒加载模式(lazy initialization)。在一些场景中无法使用,如Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

    2.3懒汉式线程安全

    public class SingletonTest {
        /**
         * 线程安全单例之synchronized
         */
        private static SingletonTest singletonTest=null;
        private SingletonTest(){};
        public static synchronized SingletonTest getInstance(){
            if(singletonTest==null){
                singletonTest=new SingletonTest();
            }
            return singletonTest;
        }
    }

    缺点:效率低。在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。

    2.4懒汉式双重检验锁

    基于2.3缺点,引出了2.4方式。线程安全。

    public class SingletonTest {
        /**
         * 线程安全单例之双重校验
         */
        private static volatile SingletonTest singletonTest=null;
        public static  SingletonTest getInstance(){
            if(singletonTest==null){//1
                synchronized(SingletonTest.class){
                    if(singletonTest==null){//2
                        //原因:多个线程一起进入同步块中,如果不进行二次检验则可能创建多个实例
                        singletonTest=new SingletonTest();
                    }
                }
            }
            return singletonTest;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(20);
            for(int i=0;i<100;i++){//线程数100
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(SingletonTest.getInstance().hashCode());
                        }
                    }
                );
            }
        }
    }

    双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

    注意:volatile修饰符。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

    1. 给 instance 分配内存
    2. 调用 Singleton 的构造函数来初始化成员变量
    3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

    但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

    我们只需要将 instance 变量声明成 volatile 就可以了。volatile实现2个功能:实现可见性和禁止指令重排优化。

    2.5静态内部类单例实现

    线程安全。

    public class SingletonTest {
        /**
         * 静态内部类方式(避免锁性能,又实现线程安全)
         * 本质和饿汉式一样
         * @param args
         */
        private static class SingleClass{//私有静态内部类
            private static final SingletonTest instance=new SingletonTest();//私有静态final属性
        }
        public static final SingletonTest  getInstance(){//公有静态final方法
            return SingleClass.instance;
        }
        
        public static void  main(String[] args){//多线程测试
            ExecutorService executor=Executors.newFixedThreadPool(20);
            for(int i=0;i<100;i++){//线程数100
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(SingletonTest.getInstance().hashCode());
                        }
                    }
                );
            }
        }

    2.6枚举Enum单例实现

    public enum EasySingleton {
        INTSTANCE;
        public void otherMethods(){
            System.out.println("Something");
        }
        
        public static void main(String[] args){
            ExecutorService executor=Executors.newFixedThreadPool(20);
            for(int i=0;i<100;i++){//线程数100
                executor.execute(
                    new Runnable(){
                        public void run(){
                            System.out.println(EasySingleton.INTSTANCE.hashCode());
                            EasySingleton.INTSTANCE.otherMethods();//调用方法
                        }
                    }
                );
            }
        }
    }

    优点:创建简单。

    1)可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。

    2)创建枚举默认就是线程安全的

    3)防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

  • 相关阅读:
    由WSDL文件生成WEB service server端C#程序(转)
    C#调用WebService实例和开发(转)
    ORA-01034: ORACLE not available ORA-27101: shared memory realm does not exist
    TOMCAT的框架结构
    Oracle学习历程--创建用户,分配表空间
    sql关键字之null
    oracle中varchar、varchar2、char和nvarchar的区别
    将中缀转换为后缀并求值
    Orchard FAQ
    盘点支持Orchard的.NET 4.5虚拟主机(虚拟空间)
  • 原文地址:https://www.cnblogs.com/cslj2013/p/9146473.html
Copyright © 2011-2022 走看看