单例模式
1.饿汉模式
类加载的时候,就进行对象的创建,系统开销较大,但是不存在线程安全
* 饿汉模式-单例对象构建方法
* (类加载的时候,就进行对象的创建,系统开销较大,影响性能,所以多数采用饿汉模式,在使用时才真正的创建单例对象)
方式一:饿汉模式:性能最差
class Singleton1 {
private static Singleton1 singleton = new Singleton1(); // 建立对象
问题:每次类加载的时候,就进行对象的创建,性能开销大,是一种空间换时间的方式
2.懒汉模式
多数采用饿汉模式,在使用时才真正的创建单例对象,但是存在线程安全
懒汉模式
线程不安全
方式二:懒汉模式-性能不安全
class Singleton2 {
private static Singleton2 singleton = null; // 不建立对象
/*synchronized 可以解决线程安全问题,但是存在性能问题,即使singleton!=null也需要先获得锁*/
public synchronized static Singleton2 getInstance() {
if (singleton == null) { // 先判断是否为空
try {
Thread.sleep(1000);
System.out.println("构建这个对象可能耗时很长...");
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new Singleton2(); // 懒汉式做法
}
return singleton;
}
是一种时间换空间的方式
问题:每次线程都要走同步代码块性能 不高
线程安全doublecheck
方式三:懒汉模式-性能安全
class Singleton3 {
private static Singleton3 singleton = null; // 不建立对象
private Singleton3() {
}
/*synchronized 代码块进行双重检查,可以提高性能*/
public static Singleton3 getInstance() {
if (singleton == null) { // 先判断是否为空
synchronized (Singleton3.class) {
//此处必须进行双重判断,否则依然存在线程安全问题
if(singleton == null){
try {
Thread.sleep(1000);
System.out.println("构建这个对象可能耗时很长...");
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new Singleton3(); // 懒汉式做法
}
}
}
return singleton;
}
这里的synchronize只有开头几个线程会进来,此时singleton为null。
为什么要进行双重判断?
因为同步代码块的存在,多个线程程序刚启动的时候会一起进来,1个线程进入代码块执行方法,结束后,其他线程执行方法前要判断这个对象是否为null,这里的判断是判断是否一初始化实例。
第一次是判断单例是否存在,准备使用
第二次是判断单例是否存在,准备创建
3.静态内部类单例
兼具懒汉模式和饿汉模式的优点
序列化方式和反射方式都可以破坏单例
定义饿汉类
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}
}
1.序列化破坏单例
序列化的时候对于普通对象会new Instance
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
控制台输出:
发现输出的是不同的地址,观察ois.readObject();的源码发现
因为对象是object的,所以走到
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));
在这个readOrdinaryObject中发现代码片
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
观察isInstantiable()方法,说明返回的一定是true
/**
* 如果表示的类是可序列化的,并且可以由序列化运行时实例化,则返回 true
* Returns true if represented class is serializable/externalizable and can
* be instantiated by the serialization runtime--i.e., if it is
* externalizable and defines a public no-arg constructor, or if it is
* non-externalizable and its first non-serializable superclass defines an
* accessible no-arg constructor. Otherwise, returns false.
*/
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
所以调用desc.newInstance(),这里就新建了一个实例,所以和原来的实例对象是不同的
拓展:序列化下实现单例
但观察源码,可以发现,如果object拥有readResolve()方法,则最后会返回这个方法的返回对象覆盖原来new Instance()对象,所以加上这个方法试试
修改HungrySingleton,在readResolve()方法中返回单例对象
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}
}
调试可得,发现输出的的确是单例
2.反射方式破坏单例
懒汉可以采用防御机制,在无参构造方法中防御,但是懒汉模式无法防御
Class objectClass = LazySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton newInstance = (LazySingleton) constructor.newInstance();
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
输出
但是饿汉模式可以在无参构造器中加上防御机制
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
3.使用枚举方式
针对序列化:
序列化的对象是确定的枚举值,所以一定是相同的
针对反射:
enum没有无参构造器
反射无法使用枚举!
通过反编译查看enum源码发现:
4.匹配源码
饿汉模式:
容器单例思想