单例模式 (Singleton)

2023-01-08 13:37:21

单例模式 (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 证明对象只会被实例化一次
    }  
}

实现方式二:


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 证明对象只会被实例化一次
    }  
}

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();
        }
    }  
}

输出结果:

769879054
391275521
819827238
819827238
391275521
956261507

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

为了解决线程不安全的问题,我们想到了可以通过 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();
        }
    }
}

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

为了解决增加 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();  
        }  
    }  
}

运行结果:

769879054
769879054
1030610325
769879054
1030610325
956261507

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

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


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();  
        }  
    }  
}

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

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();  
        }  
    }  
}

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();  
        }  
    }  
  
}

三、写在后面

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

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

  • 作者:Java咩
  • 原文链接:https://blog.csdn.net/M_Love_U/article/details/126059794
    更新时间:2023-01-08 13:37:21