确保一个类只有一个实例,并提供该实例的全局访问点。
使用一个私有构造函数、一个私有静态变量及一个公有静态函数实现。
私有构造函数保证了不能通过构造函数创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
使用场景:线程池,数据库连接池,缓冲等对象,只能有一个实例,一旦产生多个实例就会出现问题。
优点:减少内存开销,避免对资源的多重占用,设置全局访问点,严格控制访问
缺点:没有接口,拓展困难
Key Words: 私有构造、线程安全、延迟初始化、序列化与反序列化安全、防止反射
1、懒汉式(线程不安全)
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
注意到,私有静态变量uniqueInstance被延迟实例化,如果没有用到该类,就不会实例化uniqueInstance,节约资源。
多线程下不安全,如果多个线程能同时进入判断语句,且此时uniqueInstance为null,这样的话就会有多个线程执行实例化语句,导致实例化多次uniqueInstance。
以时间换空间,在多线程环境下存在风险
2、饿汉式(线程安全)
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getUniqueInstance() {
return uniqueInstance;
}
}
懒汉式线程不安全的原因是由于uniqueInstance被实例化多次,因此 直接实例化uniqueInstance 就不会有线程不安全的问题。————类加载的时候就已初始化
直接实例化的方式丢失了延迟实例化带来的节约资源的好处。
以空间换时间,故不存在线程安全问题
3、懒汉式(线程安全)
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() { }
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
只对 getUniqueInstance() 方法加锁,那么一个时间点只能有一个线程能进入该方法,避免实例化多次uniqueInstance。
但是一个线程进入该方法后,其它试图进入该方法的线程必须等待,存在过长阻塞时间,效率低下。
4、双重校验锁实现DCL(线程安全)
public class Singleton {
//uniqueInstance使用volatile关键字修饰–禁止指令重排
private volatile static Singleton uniqueInstance;
private Singleton() { }
public static Singleton getUniqueInstance() {
//双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
if (uniqueInstance == null) {
synchronized (Singleton.class) {
//再校验一次,判断是否被实例化
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
1、为什么进行双重校验?
首先判断uniqueInstance是否被实例化,如果没被实例化才对实例化语句加锁。 但是如果只有一次判断的话,两个线程都会去执行实例化语句,虽然加了锁,但是只是执行先后的问题,还是会进行两次实例化。 因此需要双重校验:
1、第一个if语句用来避免uniqueInstance已经被实例化的情况,避免非必要加锁。
2、第二个if语句进行加锁,只能有一个线程进入,不会出现uniqueInstance == null时两个线程同时实例化的情况。
2、使用volatile关键字修饰uniqueInstance
实例化对象:uniqueInstance = new Singleton(); —分三步执行:
1、为uniqueInstance 分配内存空间
2、初始化uniqueInstance
3、将uniqueInstance 指向分配的内存空间
JVM具有指令重排的特性,执行顺序可能发生变化。
–2还没执行先执行了3,另一个线程执行时对象非空,出现DCL失效问题。
volatile关键字禁止指令重排,因此可以在多线程环境下正常运行。
volatile关键字的作用:https://blog.csdn.net/wang_chaochen/article/details/116592249
5、静态内部类实现
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
当Singleton第一次被加载时,并不需要去加载SingletonHolder,只有当getUniqueInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getUniqueInstance()方法会导致虚拟机加载SingletonHolder类,
这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
延迟初始化+线程安全,但是存在传参的问题,因为静态内部类创建单例,外部无法传递参数进去。
类的初始化时机:
不同于DCL方法:
getUniqueInstance()方法,调用的是SingletonHolder.INSTANCE,取的是SingleTonHoler里的INSTANCE对象,
不管多少个线程去调用getUniqueInstance()方法,取的都是同一个INSTANCE对象,而不用去重新创建。
当getUniqueInstance()方法被调用时,SingletonHolder才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建,然后再被getUniqueInstance()方法返回出去,这点同饿汉模式。
INSTANCE在创建过程中又是如何保证线程安全?
虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,
如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。
如果在一个类的()方法中有耗时很长的操作,就可能造成多个线程阻塞
(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),
在实际应用中,这种阻塞往往是很隐蔽的。
6、饿汉式+防序列化破坏
public class Singleton implements Serializable {
private final static Singleton singleton = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
//添加readsolve函数可防止序列化的破坏
private Object readResolve() throws ObjectStreamException{
return singleton;
}
}
一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象.
不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来.
这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.
防序列化破坏
/**
-
Reads and returns “ordinary” (i.e., not a String, Class,
-
ObjectStreamClass, array, or enum constant) object, or null if object’s
-
class is unresolvable (in which case a ClassNotFoundException will be
-
associated with object’s handle). Sets passHandle to object’s assigned
-
handle.
*/
private Object readOrdinaryObject(boolean unshared) throws IOException{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) {
throw new InvalidClassException(“invalid class descriptor”);
}Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(desc.forClass().getName(), “unable to create instance”).initCause(ex);
}passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}handles.finish(passHandle);
// 这个位置调用readResolve方法
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()){
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
7、饿汉式+防止反射破坏
public class Singleton implements Serializable {
private final static Singleton singleton = new Singleton();
//由于类加载时就有实例,在私有构造方法中抛出异常可以防止反射破坏
private Singleton(){
if (singleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static Singleton getInstance(){
return singleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
8、懒汉式 + 防反射攻击
由于懒汉式是延迟初始化,无法防反射攻击
double Check + 防反射攻击
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
if(uniqueInstance != null){
throw new RuntimeException(“单例构造器禁止反射调用”);
}
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
9、枚举实现
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName(“firstName”);
System.out.println(firstSingleton.getObjName());//firstName
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName(“secondName”);
System.out.println(firstSingleton.getObjName());//secondName
System.out.println(secondSingleton.getObjName());//secondName
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());//secondName
}
} catch (Exception e) {
e.printStackTrace();
}
}
枚举在Java中与普通类一样,能拥有字段和方法,枚举实例创建是线程安全的。
在任何情况下都是一个单例Singleton.INSTANCE。
可以防止反射攻击。枚举是由JVM保证只会实例化一次。
该实现在多次序列化和序列化之后,不会得到多个实例。
防反射:
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
防反序列化:
ObjectOutputStream 的序列化方法看下 Enum 类型的序列化内容,
顺着 writeobject方法找到writeObject0方法:
// ObjectOutputStream > writeobject0()
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
}
专门的 writeEnum方法:
private void writeEnum(Enum<?> en,
ObjectStreamClass desc,
boolean unshared) throws IOException
{
// 1. ENUM类型标志(常量):“126”
bout.writeByte(TC_ENUM);
ObjectStreamClass sdesc = desc.getSuperDesc();
// 2. 完整类名:“com.chaycao.java.EnumSingleton: static final long serialVersionUID = 0L;”
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
// 3. Enum对象的名称:“INSTANCE”
writeString(en.name(), false);
}
反序列化:
使用专用的 readEnum 方法:
private Enum<?> readEnum(boolean unshared) throws IOException {
// 1. 检查标志位
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}
// 2. 检查类名是否是Enum类型
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}
int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}
String name = readString(false);
Enum<?> result = null;
// 3. 加载类,并使用类的valueOf方法获取Enum对象
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
Enum.valueOf 方法:
public static <T extends Enum> T valueOf(Class enumType, String name) {
// name = “INSTANCE”
// 根据名称查找
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException(“Name is null”);
// 没有找到,抛出异常
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + “.” + name);
}
根据名称查找对象,再返回,所以仍会返回 EnumSingleton中的INSTANCE,不会存在反序列化的危险。
1、静态内部类部分 :
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
https://blog.csdn.net/mnb65482/article/details/80458571