zoukankan      html  css  js  c++  java
  • 单例模式的七种实现Singleton(Java实现)

    1. 饿汉式

    实现代码:

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton singleton = new Singleton();
    
        public static Singleton getInstance() {
            return singleton;
        }
    }

    验证一下:

        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
    
            System.out.println(s1 == s2);
            // true
        }

    如果用反射, 是否仍然是单例:

    结果是反射破坏了单例

        public static void main(String[] args) throws Exception {
            // 自定义单例方法获取
            Singleton s1 = Singleton.getInstance();
    
            // 反射获取
            Constructor constructor = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton s2 = (Singleton) constructor.newInstance();
    
            System.out.println(s1 == s2);
            //false
        }
    

    2. 懒汉式

    将上面的饿汉式改为懒汉式:

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton singleton;
    
        public static Singleton getInstance() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    } 

    验证一下:

        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
    
            System.out.println(s1 == s2);
            // true
        }

    不过这是一种线程不安全的单例实现.

    我们在Singleton中加上sleep来模拟一下线程切换:

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton singleton;
    
        public static Singleton getInstance() {
            if (singleton == null) {
                try {
                    Thread.sleep(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

     验证一下线程不安全:

    public class Main3 {
        private static LinkedBlockingQueue<Singleton> singletons = new LinkedBlockingQueue<>();
        public static void main(String[] args) throws Exception{
            ExecutorService threadPool = Executors.newFixedThreadPool(10);
            for(int i= 0;i<100;i++){
                threadPool.execute(()->{
                    singletons.offer(Singleton.getInstance());
                });
            }
    
    
            Singleton basic = singletons.take();
            while(basic==singletons.take()){
                System.out.println("continue");
                continue;
            }
    
            System.out.println("走到这里说明单例失败");
        }
    }
    

     

    3. 懒汉式+同步方法

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

     验证一下:

        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();
            Singleton s2 = Singleton.getInstance();
    
            System.out.println(s1 == s2);
            // true
        }
    

      由于是同步, 所以同步方法内不会出现多线程执行的情况.

    4. 懒汉式+双重校验锁

    因为上面那种会每次进时都会进行同步锁, 很浪费性能, 所以在加锁之间先进行校验

    public class Singleton{
        private Singleton() {
        }
    
        private static Singleton singleton;
    
        public static Singleton getInstance() {
            if (singleton==null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    

     验证一下性能:

    能明显看出来性能差距...5千倍...

    同步方法, 即直接在方法声明处加了Synchronize的情况:

        public static void main(String[] args){
            long start = System.currentTimeMillis();
            for(int i= 0;i<999999999;i++){
                Singleton s = Singleton.getInstance();
            }
            System.out.println(System.currentTimeMillis()-start);
        }
    

     

    双重校验锁:

        public static void main(String[] args){
            long start = System.currentTimeMillis();
            for(int i= 0;i<999999999;i++){
                Singleton s = Singleton.getInstance();
            }
            System.out.println(System.currentTimeMillis()-start);
        }
    

     

    5. 懒汉式+双重校验锁+防止指令重拍

    看似简单的一段赋值语句:instance = new Singleton(); 其实JVM内部已经转换为多条指令:

    memory = allocate(); //1:分配对象的内存空间

    ctorInstance(memory); //2:初始化对象

    instance = memory; //3:设置instance指向刚分配的内存地址

    但是经过重排序后如下:

    memory = allocate(); //1:分配对象的内存空间

    instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化

    ctorInstance(memory); //2:初始化对象

    可以看到指令重排之后,instance指向分配好的内存放在了前面,而这段内存的初始化被排在了后面,在线程A初始化完成这段内存之前,线程B虽然进不去同步代码块,但是在同步代码块之前的判断就会发现instance不为空,此时线程B获得instance对象进行使用就可能发生错误。

    加上volatile关键字:

    public class Singleton {
        private Singleton() {
        }
    
        private volatile static Singleton singleton;
    
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
    

     验证一下性能:

    能明显看出来加了volatile后对性能的影响, 由之前的5, 变为了302...

    性能下降了, 但是相比于上面的双重校验锁, 更保证了线程安全.

        public static void main(String[] args){
            long start = System.currentTimeMillis();
            for(int i= 0;i<999999999;i++){
                Singleton s = Singleton.getInstance();
            }
            System.out.println(System.currentTimeMillis()-start);
        }
    

     

    6. 静态内部类

    这也是一种很好的实现方式, 不仅懒加载, 还保证了线程安全, 性能也很好, 实现起来也很简单

    public class Singleton {
        private static class LazyHolder {
            private static final Singleton instance = new Singleton();
        }
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return LazyHolder.instance;
        }
    }
    

     验证一下性能:

    public static void main(String[] args){
            long start = System.currentTimeMillis();
            for(int i= 0;i<999999999;i++){
                Singleton s = Singleton.getInstance();
            }
            System.out.println(System.currentTimeMillis()-start);
        }
    

     

    7. 枚举

    个人对枚举类型的理解还有限, 有待学习....

    public enum Singleton {
        INSTANCE;
    
        private String name;
    
        Singleton() {
            this.name = "king";
        }
        public static Singleton getInstance() {
            return INSTANCE;
        }
    
        public String getName() {
            return this.name;
        }
    }
    

     验证一下性能:

        public static void main(String[] args){
            long start = System.currentTimeMillis();
            for(int i= 0;i<999999999;i++){
                Singleton s = Singleton.getInstance();
            }
            System.out.println(System.currentTimeMillis()-start);
        }
    

     

    ---------------------------------------------------------
    学如不及,犹恐失之
  • 相关阅读:
    Java流程控制语句
    Linux文件过滤及内容编辑处理
    Java运算符优先级
    Java位运算基础知识
    【Linux】【FastDFS】FastDFS安装
    【Linux】【redis】redis安装及开启远程访问
    【Linux】【sonarqube】安装sonarqube7.9
    【Linux】【PostgreSQL】PostgreSQL安装
    【Linux】【maven】maven及maven私服安装
    【Linux】【jenkins】自动化运维七 整合sonarqube代码审查
  • 原文地址:https://www.cnblogs.com/noKing/p/java_design_patterns_Singleton.html
Copyright © 2011-2022 走看看