java单例模式的8种写法

2023年2月25日09:25:13

单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致

1 饿汉模式  推荐写法

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:24
 */
public class Test01 {
    private static final Test01  INSTANCE=new Test01();

    private  Test01(){

    }

    public static Test01 getINSTANCE() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test01.getINSTANCE().hashCode());
            }).start();
        }
    }
}

私有的构造方法使得Test01完全被封闭起来 实例化工作是自己内部的事务

private static final 修饰 保证了 INSTANCE是私有的 ,不可见的不可访问的,static保证了静态性,在类被加载进内存时,就已经初始化 ,final保证INSTANCE是常量,是不能被修改的

外部只要调用公共的方法TEST01.getINSTANCE就可以获得唯一的实例对象了 

2 饿汉模式 静态代码块赋值

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:24
 */
public class Test03 {
    private static final Test03 INSTANCE;

    static {
        INSTANCE=new Test03();
    }
    private Test03(){

    }

    public static Test03 getINSTANCE() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test03.getINSTANCE().hashCode());
            }).start();
        }
    }
}

此处将实例化操作放到静态代码块中

3 懒汉式 

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:33
 */
public class Test02 {
    private static Test02 test02;
    private Test02(){};

    public static Test02 getInstance(){
        if (test02==null){
            test02=new Test02();
        }

        return test02;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test02.getInstance().hashCode());
            }).start();
        }
    }

}

恶汉模式如果没人使用,但是却实例化对象 ,这样一块内存区不是白浪费了 这样单杀了懒汉模式的写法

只有当某一个线程第一次调用getINSTANCE时才会进行实例化操作 之后再有线程访问直接返回对象

这样程序乍看确实没什么问题 但是在多线程环境下 可能会有多个线程进入到了getINSTANCE方法内,这样就会导致原来已经实例化的对象被覆盖掉

为了保证线程安全 我们给getINSTANCE方法加上 synchronized同步锁 下面看第四种写法

4  synchronized 版懒汉模式

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:33
 */
public class Test04 {
    private static Test04 test02;
    private Test04(){};

    public static synchronized Test04 getInstance(){
        if (test02==null){
            test02=new Test04();
        }

        return test02;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test04.getInstance().hashCode());
            }).start();
        }
    }

}

这样确实没有什么问题 然而这样的做法是要付出一定代价的,试想,线程还没进入方法内部便不管三七二十一直接加锁排队,会造成线程阻塞,资源与时间被白白浪费。我们只是为了实例化一个单例对象而已,犯不上如此兴师动众,使用synchronized让所有请求排队等候。所以,要保证多线程并发下逻辑的正确性,同步锁一定要加得恰到好处

下面看第五种写法 在方法体内部加锁:

5  synchronized 内部加锁

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:33
 */
public class Test05 {
    private static Test05 test02;
    private Test05(){};

    public static  Test05 getInstance(){
        if (test02==null){
            synchronized (Test05.class){
                test02=new Test05();
            }
            
        }

        return test02;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test05.getInstance().hashCode());
            }).start();
        }
    }

}

这样在多线程环境也会有一定问题 ,可能会有多个线程同时通过了 tese02==null 的判断进入了方法里,这样也会造成重复的实例化

6  双重检查synchronized 推荐写法

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:33
 */
public class Test06 {
    private static  volatile  Test06 test02;
    private Test06(){};

    public static Test06 getInstance(){
        if (test02==null){
            synchronized (Test06.class){
                if (test02==null){
                    test02=new Test06();
                }
            }
        }

        return test02;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test06.getInstance().hashCode());
            }).start();
        }
    }

}

我们一共用了2个嵌套的判空逻辑,这就是懒加载模式的“双检锁”:外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。如此里应外合,不仅达到了单例模式的效果,还完美地保证了构建过程的运行效率,一举两得。

7  内部类方式

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:51
 */
public class Test07 {

    private Test07(){};

    private static class Test0701{
        private static final  Test07 test07=new Test07();
    }
    
    public   static  Test07 getInstance(){
        return Test0701.test07;
    }

    public static void main(String[] args) {
        for (int i=0;i<1000;i++){
            new Thread(()->{
                System.out.println(Test07.getInstance().hashCode());
            }).start();
        }
    }
    
}

8 枚举类方式

package com.cyc.mystudy.singleton;

/**
 * @Author cyc
 * @create 2022/7/30 11:57
 */
public enum Test08 {

    INSTANCE;

    public  void m(){
        System.out.println("业务代码");
    }

    public static void main(String[] args) {
        Test08.INSTANCE.m();
    }
}

在一般情况下我们使用饿汉模式,恶汉模式不用担心多线程环境会出问题,写法上也比较简单,

我们不用为了省一点性能而去给自己造成麻烦

  • 作者:咚咚呛!
  • 原文链接:https://blog.csdn.net/qq_44867168/article/details/126069831
    更新时间:2023年2月25日09:25:13 ,共 3786 字。