本文是学习悟空老师的视频教程线程八大核心基础后所做的心得笔记,想更加具体了解其中知识的小伙伴可以前往慕课网悟空老师的课程中进行学习
单例模式的八种实现方式
红色为重点内容,方法3,5为线程不安全写法,不可用
参考资料来源:慕课网八大核心线程基础
方法的具体实现代码
1.饿汉式(静态常量)
package singleton;
/**
* 饿汉式(静态常量)(可用)
* 类加载时就进行实例的初始化
*/
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
2.饿汉式(静态代码块)
/**
* 饿汉式(静态代码块)(可用)
* 同样也是在类加载时便初始化实例
*/
public class Singleton2 {
private final static Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public static Singleton2 getInstance() {
return INSTANCE;
}
}
——————————饿汉式一个需要注意的情况——————————
饿汉式如果对象的实例化需要读取配置文件的话,饿汉式便不适用了
———————————————end———————————————
3.懒汉式(线程不安全的写法)(该方法不可用)
/**
* 懒汉式(线程不安全)
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
// 不安全原因在这里(当两个线程同时进入的时候,发现instance都为null,便会实例化两个对象)
// 与单例的初衷不符合
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
4.懒汉式(线程安全,同步方法)(可以用但不推荐使用,原因:性能较低)
/**
* 描述: 懒汉式(线程安全)(不推荐)
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {
}
/**
* 给getInstance()方法加入synchronized关键字,成为了同步方法
* 每次get都要获取锁,代码性能较低,不推荐使用
*/
public synchronized static Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
5.懒汉式(线程不安全,同步代码块)(不能使用)
/**
* 懒汉式(线程不安全)(不推荐)
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (instance == null) {
// 对新建实例这行代码增加同步代码块
// 两个线程同时执行到这一句,第一个线程抢占锁,新建了一个实例并释放锁
// 第二个线程抢占第一个线程释放的锁之后,也新建了一个实例
// 这样的话,便会新建两个实例(与单例的初衷相违背)
synchronized (Singleton5.class) {
instance = new Singleton5();
}
}
return instance;
}
}
6.双重检查(推荐使用,面试重点之一)
/**
* 双重检查
*/
public class Singleton6 {
private volatile static Singleton6 instance;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (instance == null) {
synchronized (Singleton6.class) {
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
———————————————双重检查常见问题—————————————
-
首先先陈述下它的优点(线程安全,延迟加载,效率较高)
(它与4,5两种方法比较,4存在性能问题,5存在线程安全问题)
为什么要双重检查(这与5为什么会存在线程安全问题是一样的考虑情况)
-
为什么需要volatile关键字
原因:instance = new Singleton6(); 这一步操作不是原子性操作
该操作主要分为以下三步(这三步可能因为jvm虚拟机的原因,会发生重排序)
- 创建一个空对象
- 调用构造方法
- 赋值
当发生重排序的时候,线程1先给instance赋值,但没有调用构造方法给对象初始化
对象里面的参数都没有赋予内容
此时CPU将资源从线程1切换给线程2,
线程2进入,发现instance不为null,便默认为instance已经初始化完毕,
但是当线程2执行方法要使用instance里面的参数的时候,便会报空指针错误
所以要使用volatile关键字来禁止重排序
—————————————————end—————————————————
7.静态内部类(可用,也是属于懒汉模式)(当jvm加载时,只会加载外部类,不会加载内部类)
/**
* 静态内部类方式,可用
*/
public class Singleton7 {
private Singleton7() {
}
private static class SingletonInstance {
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
8.枚举类(推荐在日常工作中使用)(大佬推荐!)
/**
* 枚举单例
*/
public enum Singleton8 {
INSTANCE;
public void whatever() {
}
}
优点:写法简单,线程安全有保障,同时还避免反序列化(反射)破坏单例