单例
约 1137 字大约 4 分钟
2025-02-12
学习链接,菜鸟教程
1 什么是单例
就是有这么一个类,它只能创建自己的对象,有且只有一个自己的对象。并向外部提供一个访问这个对象的方式。
2 单例解决了什么麻烦?(为什么使用单例)
如果一个类频繁的创建然后销毁,麻烦不麻烦?很麻烦,用单例,只创建一次,每次需要的时候直接调用就行。
控制实例数目,节省系统资源。
3 单例的特点是什么?(使用单例需要注意的地方)
构造函数必须私有化。
不能继承。
需要同步锁synchronized。
解释:
为什么构造函数必须私有化?如果不私有化,那默认是public,那就可以无限使用Singleton singleton = new Singleton();那就会不断实例化单例类。违反了单例的设计模式,提供不了单例所带来的优势。
为什么不能继承?因为构造函数私有化了。如果继承,子类的构造函数默认super();父类私有化的构造函数怎么super();?
为什么需要同步锁synchronized?防止多线程进入造成单例类被多次实例化。
4 单例代码怎么实现?
线程安全是指:避免多个线程同时使用一个对象;不安全反之。Lazy初始化是指:使用到的时候才进行初始化;反之就是在类加载的时候就初始化:即时加载
4.1 懒汉式,线程不安全,Lazy初始化
不支持多线程,因为没有加锁,synchronized。
package test;
public class Singleton {
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
4.2 懒汉式,线程安全,Lazy初始化
支持多线程,加锁synchronized,但是效率低。
package test;
public class Singleton {
private Singleton(){}
private static Singleton instance;
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
4.3 饿汉式,线程安全,即时加载
没有加锁,执行效率高,类加载就初始化,浪费内存,容易产生垃圾。即时加载容易理解,类初始化的时候,就要new Singleton();那既然没有synchronized,为什么还会线程安全?因为classloader机制避免了多线程的同步问题。那什么是classloader机制?为什么避免了多线程?查了一些类加载的资料,还不是很懂,目前的理解是:多个线程同时实例化对象的时候,如果不存在Singleton对象,则触发类的初始化,如果存在Singleton对象,则直接调用。(类加载的文章链接点击这里)
package test;
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public synchronized static Singleton getInstance() {
return instance;
}
}
4.4 双检锁/双重校验锁
DCL,即 double-checked locking
线程安全,懒加载,并且高性能。其实这种单例我是没懂,单写出来记录一下。疑问点有两点。一是volatile关键字,看了例子,大概是不具备原子性,具备可见性。比synchronized更轻量级的同步机制,单原理和使用场景还没搞明白。二是双城instance == null 的目的。
package test;
public class Singleton {
private Singleton(){}
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4.5 登记式/静态内部类
此单例模式代码摘抄自菜鸟教程,因为这个好多地方我还没看懂
这种单例模式是在饿汉式单例的升级版,为什么?因为在饿汉式的基础上达到lazy loading的效果。还能达到双检锁单例的功效。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4.6 枚举式
线程安全,非懒加载。(介绍枚举式单例详细的一篇博客)实现单例最佳的方式,自动支持序列化机制,防止被多次实例化。线程安全的原因:枚举类在被虚拟机加载的时候会保证线程安全的被初始化。在序列化方面:枚举的序列化和反序列化是有特殊定制的。这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。线程安全自然不用多说,那么序列化和反序列化是什么呢?先把问题放在这儿。
package test;
public enum Singleton {
instance;//public static final Singleton instance;
}