• 设计模式——01单例模式:Singleton


    /**
    * 单例:把构造方法设置为私有的,别人(其他类里)new不了,而且全局只有这一个实例,在本类里可以,因为private的性质
    * 因为实例定义是final 且 static的,每次调用getSingleton()返回的就是这一个;
    * 所以是每次返回的都是同一个实例。这里需要注意的是实例化操作(new Singleton();)在声明其变量时就要操作;
    * 否则你放getSingleton()里,那么每次都new一个新的,那就没意义了。
    */

    1、饱汉式:

    示例1:

    package class01;
    
    public class Singleton {
        
        private static final Singleton INSTANCE = new Singleton();
        private Singleton() {}
        
        public static Singleton getSingleton() {
            return INSTANCE;
        }
        public void print() {
            System.out.println("this is Singleton");
        }
    
        public static void main(String[] args) {
            Singleton singleton1 = new Singleton();
            Singleton singleton2 = new Singleton();
            System.out.println(singleton1 == singleton2);
    
        }
    
    }

    输出为:

     false 

    解释:

    这里把new的操作放在了main中,自然是两个对象,在这里可以new,是因为在同一个类中,参见private和public的属性。

    示例2:

    package class01;
    
    public class Main {
    
        public static void main(String[] args) {
            
            //Singleton singleton = new Singleton();//不能直接new
            Singleton singleton = Singleton.getSingleton();
            Singleton singleton2 = Singleton.getSingleton();
            System.out.println(singleton == singleton2);
        }
    }

    输出:

    true

    解释:

    返回的是同一个对象。

     类加载到内存后,就实例化一个单例,JVM保证线程安全;

     唯一缺点:不管是否用到,类装载时就完成实例化(话说自己不用干嘛要实例化,是吧?)


    * 为了解决上面的问题:提出了懒汉式实例化,lazy loading,即用到的时候在new;
    * 虽然达到了按需初始化的目的,但是却带来了线程不安全的问题。
    * 变量为非final的,有final必须初始化.

    package class01;
    
    public class Singleton2 {
        
        private static Singleton2 INSTANCE;
        private Singleton2() {}
        
        public static Singleton2 getSingleton() {
            if (INSTANCE == null) {
                INSTANCE = new Singleton2();
            }
            return INSTANCE;
        }
        public void print() {
            System.out.println("this is Singleton");
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    System.out.println(Singleton2.getSingleton().hashCode());
                }).start();
            }
        }
    }

    但是这样就会造成线程不安全。编写如上main方法,来揭示在多线程下得到的实例是不是同一个对象,为了区分明显,我们可以在getSingleton(){}方法内的  if (){} 里写入睡眠语句,如下:

        public static Singleton2 getSingleton() {
            if (INSTANCE == null) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                INSTANCE = new Singleton2();
            }
            return INSTANCE;
        }

    运行后,输出:

    512585151
    945515189
    945515189
    76872907
    76872907
    945515189
    945515189
    945515189
    945515189
    945515189

    即发现hashcode并不完全一致(虽然完全一致并不代表是同一个对象,但是不一致肯定不是同一个对象)。

    so...........

    为了改善线程不安全,我们可以使用synchronized来给方法加锁。
    但是这样就效率低下了。

        public static synchronized Singleton2 getSingleton() {
            if (INSTANCE == null) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                INSTANCE = new Singleton2();
            }
            return INSTANCE;
        }

    为了改进这个问题,我们可以使用代码块加锁的方式,只对方法里不安全的代码进行加锁。

     1     public static Singleton2 getSingleton() {
     2         if (INSTANCE == null) {
     3             synchronized (Singleton2.class) {
     4                 try {
     5                     Thread.sleep(1);
     6                 } catch (InterruptedException e) {
     7                     // TODO Auto-generated catch block
     8                     e.printStackTrace();
     9                 }
    10                 INSTANCE = new Singleton2();
    11             }
    12         }
    13         return INSTANCE;
    14     }

    执行结果为:

    945515189
    945515189
    945515189
    945515189
    945515189
    945515189
    512585151
    512585151
    512585151
    76872907

    我们发现这样做,依然不是线程安全的。

    为什么呢?

    原因在于:当线程A 进入到第2 行时,此时A 暂停,线程B 也进入第2 行,之后B 线程取得锁,B 线程继续执行new 操作完毕,释放锁。

    之后才轮到A 去执行,此时拿到锁,这里就出现问题了,下一步A 仍旧会去再执行new 操作。这样就new 了两次。

    为了避免面这个问题。特此引入双重判空结构。

    而且,变量INSTANCE要设置成: private static volatile Singleton2 INSTANCE; ,需要加上关键词 volatile,关于该关键词的用法及详解大家可以参考我这篇文档,Java之先行发生原则与volatile关键字详解

        public static Singleton2 getSingleton() {
            if (INSTANCE == null) {
                synchronized (Singleton2.class) {
                    if (INSTANCE == null) {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        INSTANCE = new Singleton2();
                    }
                }
            }
            return INSTANCE;
        }

    输出:

    512585151
    512585151
    512585151
    512585151
    512585151
    512585151
    512585151
    512585151
    512585151
    512585151

    hashcode都一致。

    静态内部类方式

    JVM保证单例

    加载外部类时不会加载静态内部类,这样可以实现懒加载。

    package class01;
    
    public class Singleton3 {
        
        /**
         * 静态内部类方式
         * JVM保证单例
         * 加载外部类时不会加载内部类,这样可以实现懒加载。
         */
        private Singleton3() {}
        
        private static class SingletonHolder {
            private static final Singleton3 INSTANCE = new Singleton3();
        }
        
        public static Singleton3 getSingleton() {
            return SingletonHolder.INSTANCE;
        }
        
        public void print() {
            System.out.println("this is Singleton");
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    System.out.println(Singleton3.getSingleton().hashCode());
                }).start();
            }
        }
    }

    输出:

    1172810421
    1172810421
    1172810421
    1172810421
    1172810421
    1172810421
    1172810421
    1172810421
    1172810421
    1172810421

    hashcode都一致。

    这里的线程安全是JVM保证的。

    最后一种是最完美的写法。

    但是,JAVA的创始人不乐意了,干出了一个更完美的写法:在《Effective Java》书中给出了这样的例子:

    package class01;
    
    public enum Singleton4 {
        /**
         * 不仅可以解决线程同步问题,还可以防止反序列化
         */
        INSTANCE;
        public void m() {}
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                new Thread(()->{
                    System.out.println(Singleton4.INSTANCE.hashCode());
                }) .start();
            }
        }
    }

    大家注意:这里是enum 枚举,而不是class。另外枚举不需要构造函数

    输出:

    76872907
    76872907
    76872907
    76872907
    76872907
    76872907
    76872907
    76872907
    76872907
    ...

    总结

    一般情况下是用第一种和第四种的,因为比较简单。

    Over....

  • 相关阅读:
    autocomplete="off" 不起作用
    IE8兼容模式设置
    H5学习
    wampserver安装配置
    HTML5音乐播放器(最新升级改造加强版)
    HTML5+CSS3+jquery实现简单的音乐播放器
    jquery+css3实现3d滚动旋转
    HTML5游戏设计与开发 小白7-9月的动态
    jquery submit()不执行
    html5手机常见问题与工具分享
  • 原文地址:https://www.cnblogs.com/gjmhome/p/14828572.html
Copyright © 2020-2023  润新知