• 单例模式 (Singleton)


    单例模式 (Singleton)

    在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。

    一、应用场景

    • 只需要一个实例
      • 比如各种Mgr
      • 比如各种Factory

    二、分类与实现

    1. 饿汉模式

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

    优点:

    简单实用,推荐使用!

    唯一缺点:

    不管用到与否,类装载时就完成实例化 。Class.forName(“”) (话说你不用的,你装载它干啥)

    代码实现:

    实现方式一:

    
    public class Mgr01 {
    
    	// 类加载时对象被实例化
        private static final Mgr01 INSTANCE = new Mgr01();  
    
    	// 构造方法私有,保证不会被多次实例化
        private Mgr01() {};
    
    	// 返回实例化对象
        public static Mgr01 getInstance() {  
            return INSTANCE;  
        }
    
        public void m() {
            System.out.println("m");  
        }
    
    	// for test 
        public static void main(String[] args) {  
            Mgr01 m1 = Mgr01.getInstance();
            Mgr01 m2 = Mgr01.getInstance();
            System.out.println(m1 == m2); // true 证明对象只会被实例化一次
        }  
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    实现方式二:

    
    public class Mgr02 {
    
        private static final Mgr02 INSTANCE;
    
    	// 通过静态代码块让对象实例化
        static {
            INSTANCE = new Mgr02();
        }
        
        private Mgr02() {};
        
        public static Mgr02 getInstance() {
            return INSTANCE;  
        }
        
        public void m() {
            System.out.println("m");
        }
        
        public static void main(String[] args) {
            Mgr02 m1 = Mgr02.getInstance();
            Mgr02 m2 = Mgr02.getInstance();
            System.out.println(m1 == m2); // true 证明对象只会被实例化一次
        }  
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2. 懒汉模式 (lazy loading)

    为了解决 饿汉模式 中,不管用到与否,类装载时就完成实例化的问题,提出了懒汉模式。

    即,只有类在被用到时,才会被实例化。

    优点:

    达到了按需初始化的目的

    缺点:

    可能会带来线程不安全的问题

    实现方式一:(线程不安全)

    类在被用到时,才会被实例化,即,如果该对象没有被实例化,才会去实例化该对象性。
    但是会造成线程不安全的新问题。

    
    public class Mgr03 {
    
        private static Mgr03 INSTANCE;
      
        private Mgr03() {
        }
        
        public static Mgr03 getInstance() {
            if (INSTANCE == null) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr03();
            }
            return INSTANCE;
        }
      
        public void m() {  
            System.out.println("m");
        }  
      
        public static void main(String[] args) {
            for(int i=0; i<100; i++) {
                new Thread(()->
                    System.out.println(Mgr03.getInstance().hashCode())
                    ).start();
            }
        }  
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出结果:

    769879054
    391275521
    819827238
    819827238
    391275521
    956261507
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现方式二:(线程安全,但是效率低下)

    为了解决线程不安全的问题,我们想到了可以通过 synchronized 解决。
    但是,同样也带来了效率低下的新问题。

    
    public class Mgr04 {
    	
        private static Mgr04 INSTANCE;  
    	
        private Mgr04() {  
        }
    	
    	// 加 synchronized 保证线程安全
        public static synchronized Mgr04 getInstance() {
    	    // 如果该类位被实例化,则进行实例化
            if (INSTANCE == null) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr04();
            }
            return INSTANCE;
        }
    	
        public void m() {
            System.out.println("m");
        }
    	
        public static void main(String[] args) {
            for(int i=0; i<100; i++) {
                new Thread(()->{
                    System.out.println(Mgr04.getInstance().hashCode());
    			}).start();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    实现方式三:(效率提升,线程不安全)

    为了解决增加 synchronized 后效率低下的问题,可以尝试减小 synchronized 的作用范围。
    但是发现不可行,有带来了线程不安全的问题。

    
    public class Mgr05 {
    	
        private static Mgr05 INSTANCE;  
    	
        private Mgr05() {  
        }  
        public static Mgr05 getInstance() {  
            if (INSTANCE == null) {  
                //妄图通过减小同步代码块的方式提高效率,然后不可行  
                synchronized (Mgr05.class) {  
                    try {  
                        Thread.sleep(1);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    INSTANCE = new Mgr05();
                }
            }
            return INSTANCE;
        }
    	
        public void m() {
            System.out.println("m");
        }  
    	
        public static void main(String[] args) {
            for(int i=0; i<100; i++) {
                new Thread(()->{
                    System.out.println(Mgr05.getInstance().hashCode());
                    }).start();  
            }  
        }  
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    运行结果:

    769879054
    769879054
    1030610325
    769879054
    1030610325
    956261507
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现方式四:(效率提升,线程安全)

    为了解决上述问题,可与采用双重判断来实现懒汉模式

    
    public class Mgr06 {
    
        private static volatile Mgr06 INSTANCE; //JIT  
      
        private Mgr06() {  
        }  
        public static Mgr06 getInstance() {  
            if (INSTANCE == null) {
                synchronized (Mgr06.class) {
    	            //双重检查
                    if(INSTANCE == null) {  
                        try {  
                            Thread.sleep(1);  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                        INSTANCE = new Mgr06();  
                    }  
                }  
            }  
            return INSTANCE;  
        }  
      
        public void m() {  
            System.out.println("m");  
        }  
      
        public static void main(String[] args) {  
            for(int i=0; i<100; i++) {  
                new Thread(()->{  
                    System.out.println(Mgr06.getInstance().hashCode());
    			}).start();  
            }  
        }  
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    实现方式五:(通过静态内部类方式来实现懒汉模式)

    JVM保证单例,加载外部类时不会加载内部类,这样可以实现懒加载

    
    public class Mgr07 {  
    
    	// 将默认构造方法私有化
        private Mgr07() {  
        }  
    
    	// 声明一个静态内部类 持有单例类 Mgr7
        private static class Mgr07Holder {  
            private final static Mgr07 INSTANCE = new Mgr07();  
        }  
      
        public static Mgr07 getInstance() {  
            return Mgr07Holder.INSTANCE;  
        }  
      
        public void m() {  
            System.out.println("m");  
        }  
      
        public static void main(String[] args) {  
            for(int i=0; i<100; i++) {  
                new Thread(()->{  
                    System.out.println(Mgr07.getInstance().hashCode());
    			}).start();  
            }  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    3. 通过枚举类来实现单例模式

    不仅可以解决线程同步,还可以防止反序列化。

    
    public enum Mgr08 {  
      
        INSTANCE;  
      
        public static Mgr08 getInstance() {  
            return INSTANCE;  
        }  
      
        public void m() {  
            System.out.println("m");  
        }  
      
        public static void main(String[] args) {  
            for(int i=0; i<100; i++) {  
                new Thread(()->{  
                    System.out.println(Mgr08.getInstance().hashCode());
    			}).start();  
            }  
        }  
      
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    三、写在后面

    欢迎关注,会经常记录一些设计模式学习的笔记。

    欢迎随时留言讨论,与君共勉,知无不答!

  • 相关阅读:
    Python最新学习路线
    PTA_乙级_1006
    远程控制桌面软件是否支持远程防护墙配置
    10分钟!搞懂计算机CPU实现原理
    【Java笔试强训】Day4(WY33 计算糖果、DD5 进制转换)
    分布式与集群区别
    《非线性成长》阅读笔记
    Flutter高仿微信-第26篇-新的朋友
    unity-webgl 转换 微信小游戏[简单上手][开箱可用]
    21天打卡挑战学习MySQL——《SQL基础入门》第二周 第四篇
  • 原文地址:https://blog.csdn.net/M_Love_U/article/details/126059794