前言
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉单例
是否多线程安全:是
是否懒加载:否
正如名字含义,饿汉需要直接创建实例。
public class EhSingleton {
private static EhSingleton ehSingleton = new EhSingleton();
private EhSingleton() {}
public static EhSingleton getInstance(){
return ehSingleton;
}
}
缺点:类加载就初始化,浪费内存 优点:没有加锁,执行效率高。还是线程安全的实例。
懒汉单例
懒汉单例,在类初始化不会创建实例,只有被调用时才会创建实例。
非线程安全的懒汉单例
是否多线程安全:否
是否懒加载:是
public class LazySingleton {
private static LazySingleton ehSingleton;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (ehSingleton == null) {
ehSingleton = new LazySingleton();
}
return ehSingleton;
}
}
实例在调用 getInstance
才会创建实例,这样的优点是不占内存,在单线程模式下,是安全的。但是多线程模式下,多个线程同时执行 if (ehSingleton == null)
结果都为 true
,会创建多个实例,所以上面的懒汉单例是一个线程不安全的实例。
加同步锁的懒汉单例
是否多线程安全:是
是否懒加载:是
为了解决多个线程同时执行 if (ehSingleton == null)
的问题,getInstance
方法添加同步锁,这样就保证了一个线程进入了 getInstance
方法,别的线程就无法进入该方法,只有执行完毕之后,其他线程才能进入该方法,同一时间只有一个线程才能进入该方法。
public class LazySingletonSync {
private static LazySingletonSync lazySingletonSync;
private LazySingletonSync() {}
public static synchronized LazySingletonSync getInstance() {
if (lazySingletonSync == null) {
lazySingletonSync =new LazySingletonSync();
}
return lazySingletonSync;
}
}
这样配置虽然保证了线程的安全性,但是效率低,只有在第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。
双重检验懒汉单例
是否多线程安全:是
是否懒加载:是
使用 synchronized
声明的方法,在多个线程访问,比如A线程访问时,其他线程必须等待A线程执行完毕之后才能访问,大大的降低的程序的运行效率。这个时候使用 synchronized
代码块优化执行时间,减少锁的粒度。
双重检验首先判断实例是否为空,然后使用 synchronized (LazySingletonDoubleCheck.class)
使用类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其他代码都不走 if (lazySingletonDoubleCheck == null)
里面,只会在最开始的时候效率变慢。而 synchronized
里面还需要判断是因为可能同时有多个线程都执行到 synchronized (LazySingletonDoubleCheck.class)
,如果有一个线程线程新建实例,其他线程就能获取到 lazySingletonDoubleCheck
不为空,就不会再创建实例了。
public class LazySingletonDoubleCheck {
private static LazySingletonDoubleCheck lazySingletonDoubleCheck;
private LazySingletonDoubleCheck() {}
public static LazySingletonDoubleCheck getInstance() {
if (lazySingletonDoubleCheck == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (lazySingletonDoubleCheck == null) {
lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
}
}
}
return lazySingletonDoubleCheck;
}
}
静态内部类
是否多线程安全:是
是否懒加载:是
外部类加载时,并不会加载内部类,也就不会执行 new SingletonHolder()
,这属于懒加载。只有第一次调用 getInstance()
方法时才会加载 SingletonHolder
类。而静态内部类是线程安全的。
静态内部类为什么是线程安全
静态内部类利用了类加载机制的初始化阶段 getInstance()
方法时,虚拟机才会加载 SingletonHolder
静态内部类,
然后在加载静态内部类,该内部类有静态变量,JVM会改内部生成
虚拟机会保证
这种方式不仅实现延迟加载,也保障线程安全。
public class StaticClass {
private StaticClass() {}
private static class SingletonHolder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static final SingletonHolder getInstance() {
return SingletonHolder.INSTANCE;
}
}
总结
-
饿汉单例类加载就初始化,在没有加锁的情况下实现了线程安全,执行效率高。但是无论有没有调用实例都会被创建,比较浪费内存。 -
为了解决内存的浪费,使用了懒汉单例,但是懒汉单例在多线程下会引发线程不安全的问题。 -
不安全的懒汉单例,使用 synchronized
声明同步方法,获取实例就是安全了。 -
synchronized
声明方法每次线程调用方法,其它线程只能等待,降低了程序的运行效率。 -
为了减少锁的粒度,使用 synchronized
代码块,因为只有少量的线程获取实例,实例是null,创建实例之后,后续的线程都能获取到线程,也就无需使用锁了。可能多个线程执行到synchronized
,所以同步代码块还需要再次判断一次。 -
静态内部类赋值实际是调用 方法,而虚拟机保证 方法使用锁,保证线程安全。
原文始发于微信公众号(小码Code):五种方式实现 Java 单例模式
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72482.html