带你认识单例模式之七种不同实现方式,最后一种堪称王炸,你绝对没见过!

单例模式之七种不同实现方式

一、饿汉式

顾名思义,这是一个饿汉子,上来就将食物塞进去(实例化),这种方式最简单粗暴

实现方式

java 复制代码
/**
 * 饿汉式单例模式
 */
public final class HungrySingleton {

    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
        System.out.println("HungrySingleton is created");
    }

    public static HungrySingleton getInstance() {
        return instance;
    }

}

测试代码

java 复制代码
public static void testHungrySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName());
                    HungrySingleton instance = HungrySingleton.getInstance();
                    System.out.println(instance);
                }
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
HungrySingleton is created

二、懒汉式

懒汉式,这个方法非常懒,就像我上学时一样,作业不写,偏偏等到要上学的前几天才开始动手疯狂补作业。

java 复制代码
/**
 * 懒汉式单例模式
 * 不要一开始就初始化实例,而是等到要用到的时候再去初始化实例
 */
public final class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton() {
        System.out.println("LazySingleton constructor");
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

}

测试代码

java 复制代码
/**
* 懒汉式单例模式
*/
public static void testLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                LazySingleton instance = LazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor

总结

上面代码在多线程情况下出现了多次实例化的情况,假如线程1正在做if (instance == null)判断,此时CPU开始调度,线程2进入if (instance == null)并且创建了一个实例,然后放弃CPU使用权,当线程1获取到CPU使用权之后就会从停止的地方继续开始执行未完成的代码,此时就会再一次实例化,出现多次实例化的情况。因此这种情况在多线程情况下并不能保证单例

三、synchronized方式

为了解决懒汉式多线程情况下多次实例化的问题,简单粗暴的方式就是加锁,直接getInstance方法上锁,保证仅一个线程同时访问此方法。

java 复制代码
public class SynchronizedLazySingleton {

    private static SynchronizedLazySingleton instance = null;

    private SynchronizedLazySingleton() {
        System.out.println("Synchronized Lazy Singleton");
    }

    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }

}

测试代码

java 复制代码
public static void testSynchronizedLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedLazySingleton instance = SynchronizedLazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
Synchronized Lazy Singleton

思考

上面加锁的粒度是不是太大了,多线程情况下同时仅有一个线程才能访问getInstance方法了,有没有办法再优化一下呢,下面就是双重检查的实现方式了。

四、双重检查(double check)

在线程进入时判断一次,为空则加锁,进入之后再判断一次,至于为什么要再判断一次,这是为了防止两次实例化。假设线程1进入了第一个判断,此时释放了CPU;线程2进来之后,同样可以执行进入第一个判断并且加锁,然后实例化,最后释放CPU;然后等到线程1再次拿到CPU之后,从上一次未执行的地方开始,对DoubleCheckLazySingleton.class加锁,由于没有第二个判断,此时线程1就可以完成第二次实例化了;因此,这就是为什么要加两次判断的原因。

java 复制代码
/**
 * 双重检查懒汉式单例模式
 */
public class DoubleCheckLazySingleton {

    private static DoubleCheckLazySingleton instance = null;

    private DoubleCheckLazySingleton() {
        System.out.println("DoubleCheckLazySingleton is created");
    }

    public static DoubleCheckLazySingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLazySingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

}

测试代码

java 复制代码
    public static void testDoubleCheckLazySingleton() {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    DoubleCheckLazySingleton instance = DoubleCheckLazySingleton.getInstance();
                }
            }, "Thread" + i).start();
        }
    }

运行结果

java 复制代码
DoubleCheckLazySingleton is created

五、双重检查+可见性(double check + volatile)

为什么要加volatile,因为我代码没有写完,如果此时单例类中还有其他的字段,就容易出现空指针异常,因为这些字段对于其它线程是不可见的,需要加上volatile保证对于其它线程也可见。

java 复制代码
/**
 * 双重检查懒汉式单例模式+volatile
 */
public class VolatileDoubleCheckLazySingleton {

    private volatile static VolatileDoubleCheckLazySingleton instance = null;

    private VolatileDoubleCheckLazySingleton() {
        System.out.println("VolatileDoubleCheckLazySingleton construct");
    }

    public static VolatileDoubleCheckLazySingleton getInstance() {
        if (instance == null) {
            synchronized (VolatileDoubleCheckLazySingleton.class) {
                if (instance == null) {
                    instance = new VolatileDoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

}

测试代码

java 复制代码
/**
* 双重检查单例模式 + volatile
*/
public static void testVolatileDoubleCheckLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                VolatileDoubleCheckLazySingleton instance = VolatileDoubleCheckLazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
VolatileDoubleCheckLazySingleton construct

六、holder内部类实例化

借助于内部类来实例化单例,秀不秀?

java 复制代码
public class HolderSingleton {
    private static HolderSingleton instance;

    private HolderSingleton() {
        System.out.println("HolderSingleton constructor");
    }

    private static class Holder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }

    public static HolderSingleton getInstance() {
        return Holder.INSTANCE;
    }

}

测试代码

java 复制代码
/**
* Holder
*/
public static void testHolderSingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HolderSingleton instance = HolderSingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
HolderSingleton constructor

七、枚举类实现方式

王炸!枚举类天然的定义就是单例,这没的说。

java 复制代码
public enum EnumSingleton {

    INSTANCE,
    ;

    private EnumSingleton() {
        System.out.println("INSTANCE will be initialized immediately");
    }

    public static void method() {

    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

}

测试代码

java 复制代码
/**
* EnumSingleton
*/
public static void testEnumSingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                EnumSingleton instance = EnumSingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
INSTANCE will be initialized immediately
相关推荐
程序媛学姐2 分钟前
SpringKafka错误处理:重试机制与死信队列
java·开发语言·spring·kafka
2401_8401922710 分钟前
如何学习一门计算机技术
开发语言·git·python·devops
向阳25619 分钟前
SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
java·vue.js·spring boot·后端·sa-token·springboot·登录流程
巷北夜未央24 分钟前
Python每日一题(14)
开发语言·python·算法
XiaoLeisj36 分钟前
【MyBatis】深入解析 MyBatis XML 开发:增删改查操作和方法命名规范、@Param 重命名参数、XML 返回自增主键方法
xml·java·数据库·spring boot·sql·intellij-idea·mybatis
风象南36 分钟前
SpringBoot实现数据库读写分离的3种方案
java·spring boot·后端
振鹏Dong43 分钟前
策略模式——本质是通过Context类来作为中心控制单元,对不同的策略进行调度分配。
java·策略模式
ChinaRainbowSea1 小时前
3. RabbitMQ 的(Hello World) 和 RabbitMQ 的(Work Queues)工作队列
java·分布式·后端·rabbitmq·ruby·java-rabbitmq
雾月551 小时前
LeetCode 914 卡牌分组
java·开发语言·算法·leetcode·职场和发展
Y.O.U..1 小时前
今日八股——C++
开发语言·c++·面试