目录
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例,这些应用都或多或少具有资源管理器的功能。选择单例模式就是为了避免不一致状态,同时也可以避免内存浪费。
单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、双重检查锁单例、静态内部类单例、枚举单例、登记式单例。
什么是单例模式
单例模式:属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例,该类自行实例化并向整个系统提供这个实例的公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
单例模式的类型
懒汉式:
1.懒汉模式(线程不安全)
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 懒汉模式(线程不安全)
* @date 2023/04/02 09:55:33
*/
public class Singleton_Lazy_1 {
private static Singleton_Lazy_1 instance;
private Singleton_Lazy_1() {
}
public static synchronized Singleton_Lazy_1 getInstance() {
if (null != instance) {
return instance;
}
instance = new Singleton_Lazy_1();
return instance;
}
}
此种模式是线程不安全,但把锁加到方法上,每次访问都因需要锁占用导致资源的浪费。除非特殊情况下,不建议此种方式实现单例模式。
2.懒汉模式(线程安全)
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 懒汉模式(线程安全)
* @date 2023/04/02 09:55:33
*/
public class Singleton_Lazy_2 {
private static Singleton_Lazy_2 instance;
private Singleton_Lazy_2() {
}
public static synchronized Singleton_Lazy_2 getInstance() {
if (null != instance) {
return instance;
}
instance = new Singleton_Lazy_2();
return instance;
}
}
此种模式是线程安全,当方法getInstance()出现并发访问时,会出现多个实例的情况,不建议此种方式实现单例模式。
饿汉式:
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 饿汉模式(线程安全)
* @date 2023/04/02 09:30:33
*/
public class Singleton_Hungry_1 {
private static Singleton_Hungry_1 instance = new Singleton_Hungry_1();
private Singleton_Hungry_1() {
}
public static Singleton_Hungry_1 getInstance() {
return instance;
}
}
双重检查锁式:
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 双重检查锁模式(线程安全)
* @date 2023/04/02 09:55:33
*/
public class Singleton_Lock_1 {
private static volatile Singleton_Lock_1 instance;
private Singleton_Lock_1() {
}
public static Singleton_Lock_1 getInstance() {
if (null == instance) {
synchronized (Singleton_Lock_1.class){
if (null == instance){
instance = new Singleton_Lock_1();
}
}
}
return instance;
}
}
执行双重检测原因:如果多个线程通过了第一次检测,此时因为synchronized,其中一个线程会首先通过了第二次检测并实例化了对象,剩余的线程不会再重复实例化对象。这样,除了初始化的时候会加锁,后续的调用都是直接返回,解决了多余的性能消耗。
外层判断:完成实例化后,之后的线程就不需要再执行synchronized等待,提高效率。
内层判断:防止多次实例化。
静态内部类式:
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 静态内部类模式(线程安全)
* @date 2023/04/02 09:55:33
*/
public class Singleton_Inner_1 {
private Singleton_Inner_1() {
}
private static class InnerSingleton {
static Singleton_Inner_1 instance = new Singleton_Inner_1();
}
public static Singleton_Inner_1 getInstance() {
return InnerSingleton.instance;
}
}
资源利用率高,不执行getInstance()不被实例,可以执行该类其他静态方法,但是第一次加载时反应不够快。
枚举式:
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 枚举模式(线程安全)
* @date 2023/04/02 10:34:39
*/
public enum Singleton_Enum_1 {
INSTANCE;
public void businessMethod() {
System.out.println("昂焱数据 我是一个单例!");
}
}
Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
这种方式解决了最主要的;线程安全、自由串行化、单一实例。
这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了串行化机制,绝对防止对此实例化,即使是在面对复杂的串行化或者反射攻击的时候。虽然该方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
登记式:
import java.util.HashMap;
/**
* @author Evan Walker
* @version 1.0
* @web 昂焱数据 https://www.ayshuju.com
* @desc 单例的 登记模式
* @date 2023/04/02 09:55:33
*/
public class Singleton_Registry_1 {
/**
* 使用一个map来当注册表
*/
private static HashMap registry = new HashMap();
//静态块,在类被加载时自动执行,把Registry Singleton自己也纳入容器管理
static {
Singleton_Registry_1 rs = new Singleton_Registry_1();
registry.put(rs.getClass().getName(), rs);
}
/**
* 受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点
*/
protected Singleton_Registry_1() {
}
/**
* 静态工厂方法,返回此类的唯一实例
*/
public static Singleton_Registry_1 getInstance(String name) {
if (name == null) {
name = Singleton_Registry_1.class.getName();
}
if (registry.get(name) == null) {
try {
registry.put(name, Class.forName(name).newInstance());
} catch (Exception ex) {
ex.printStackTrace();
}
}
return (Singleton_Registry_1) registry.get(name);
}
}
登记式单例将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
单例模式的特点
特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
优点
- 系统中只存在一个共用的实例对象,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能
- 可以严格控制客户怎么样以及何时访问单例对象。
缺点
- 可能的开发混淆,使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
- 不适用于变化的对象,单例对象的拓展有很大的困难。
适用场景
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
使用场景
- 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
- 控制资源的情况下,方便资源之间的互相通信。如线程池等。
更多消息资讯,请访问昂焱数据(https://www.ayshuju.com)