你真的理解单例模式了吗
- 定义:保证一个类仅有一个实例,并提供一个全局访问点
- 类型:创建型
- 使用场景:想确保任何情况下都绝对只有一个实例,单服务下的网站计数器,集群服务下的共享计数器,线程池、数据库的连接池、windows中的任务管理器等
- 优点:在内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用;设置了全局点,严格控制访问
- 缺点:没有接口,扩展困难
饿汉式
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
相对简单就不过多讨论了
懒汉式
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
以上代码在单线程环境中完全能够满足需要,但是一旦在多线程的环境中单例的可靠性就不能保证了
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
public synchronized static LazySingleton getInstance(){
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
解决方案将该方法加一个同步锁,但众所周知,加锁、释放锁操作是个开销蛮大的操作,能不能进一步优化呢
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance == null) {
synchronized (LazySingleton.class){
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
大名鼎鼎的双重检查上场了,这样的话,就不必每个getInstance方法都加上一道锁,但是这样会不会还有什么问题?会的。new并不是原子操作,cpu在执行过程中可能会将new的指令进行重排序。
new 大致分为三个步奏
1、分配一个内存给这个对象 2、给这个对象进行初始化 3、将地址赋给instance引用
这里的二三两步可能会交换执行顺序
1、分配一个内存给这个对象 3、将地址赋给instance引用 2、给这个对象进行初始化
那么可能另外一个线程得到的实例对象可能是未初始化完成的
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance == null) {
synchronized (LazySingleton.class){
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
解决方案就是将该引用变量使用volatile修饰,该关键字的内存语义是,1、对其它线程立即可见;2、禁止指令重排序。由此完美解决问题
还有没有什么方案可以实现懒加载呢?答案是有的,可以使用静态内部类
public class LazySingleton {
private static class InnerClass{
public static LazySingleton instance = new LazySingleton();
}
private LazySingleton(){}
public static LazySingleton getInstance(){
return InnerClass.instance;
}
}
在这里会不会产生线程安全的问题呢?其实类加载的时候JVM会帮我们上一道锁。所以没必要当心。
看到这里大家会不会认为这已经完美了,但是我还想杠一杠
实例化对象好像不只使用new指令一条路子。如果使用的其它方式好像这个单例也不是太单例
- 反序列化来了,如果有序列化反序列化的需要实现了Serializable接口,就需要注意这个问题了
public class Main {
public static void main(String[] args) throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton_file"));
HungrySingleton o = (HungrySingleton) ois.readObject();
System.out.println(instance == o);//false
}
}
如何避免这个问题呢?
public class HungrySingleton implements Serializable {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
public Object readResolve(){
return instance;
}
}
在类里补充一个实例方法readResolve,通过源码阅读我们可以发现
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//....省略的一些代码
Object obj;
try {
// 如果类实现了Serializable接口 就反射创建实例对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
//...
}
// ...
// 如果这个对象实现了readResolve方法,就返回的该方法返回的引用
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
//...
if (rep != obj) {
// Filter the replacement object
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
- 反射来了
public class Main {
public static void main(String[] args) throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
Class<HungrySingleton> cls = HungrySingleton.class;
Constructor<HungrySingleton> constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
System.out.println(instance == instance1);
}
}
反射调用近乎无解,如果真正的搞一个单例对象,我们可以尝试Enum
public enum EnumSingleton {
INSTANCE;
}
序列化和反序列化试一下
public class Main {
public static void main(String[] args) throws Exception {
EnumSingleton instance = EnumSingleton.INSTANCE;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enum_singleton"));
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enum_singleton"));
EnumSingleton instance1 = (EnumSingleton) ois.readObject();
System.out.println(instance);//INSTANCE
System.out.println(instance1);//INSTANCE
System.out.println(instance == instance1);//true
}
}
强行用反射的话会抛异常
public class Main {
public static void main(String[] args) throws Exception {
EnumSingleton instance = EnumSingleton.INSTANCE;
Class<EnumSingleton> cls = EnumSingleton.class;
Constructor<EnumSingleton> constructor = cls.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingleton instance1 = constructor.newInstance("niyulu", 6);
System.out.println(instance);
System.out.println(instance1);
System.out.println(instance == instance1);
}
}
output
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at top.luyuni.design.pattern.creational.singleton.Main.main(Main.java:11)
分析序列化不创建新对象原因
private Enum<?> readEnum(boolean unshared) throws IOException {
// 拿到枚举名称
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
// 直接从常量池中获取枚举类
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
//...
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
分析反射报错原因
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
// 如果类型为枚举直接抛一个不支持的异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
使用工具反编译枚举类
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(top/luyuni/design/pattern/creational/singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
由反编译结果可知私有构造器,类加载时创建对象完全符合饿汉式的单例模式,并且还有io,和reflect相关类保驾护航