单例模式:确保一个类在任何情况下都是绝对只有一个实例
1.饿汉式单例模式
- 饿汉式单例模式在类加载的时候就初始化,并创建单例对象,绝对的线程安全,在线程还没有出现之前就实例化了,不可能存在线程安全问题
- 优点
没有加任何锁,执行效率较高,用户体验比懒汉式单例模式更好 - 缺点
类加载的时候就初始化,始终占用空间,浪费内存
代码:
public class HungrySingleton {
private static final HungrySingleton hSingleton = new HungrySingleton();
public static HungrySingleton getInstance(){
return hSingleton;
}
}
或者利用静态代码块
public class HungrySingleton {
// private static final HungrySingleton hSingleton = new HungrySingleton();
private static final HungrySingleton hSingleton;
static {
hSingleton=new HungrySingleton();
}
public static HungrySingleton getInstance(){
return hSingleton;
}
}
2. 懒汉式单例模式
- 只有在被外部类调用的时候内部类才会被加载
代码实现
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton =null;
public static LazySimpleSingleton getInstance(){
if (lazySimpleSingleton==null){
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}
通过两个线程测试懒汉式单例模式
public class TestThread implements Runnable {
@Override
public void run() {
LazySimpleSingleton simpleSingleton = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName()+":"+simpleSingleton);
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new TestThread());
Thread t2 = new Thread(new TestThread());
t1.start();
t2.start();
System.out.println("End");
}
}
结果可能会出现不一样的实例:也就意味着上述单例模式存在线程安全隐患
解决办法:
- 通过使用synchronize关键字,但是上锁,对程序性能还是存在一定的影响
- 方法二
代码如下
public class LazyInnerClassSingleton {
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY ;
}
private static class LazyHolder{
// static:单例的空间共享,保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
- 这种方式兼顾饿汉式单例模式的内存问题和synchronize的性能问题
- 没有使用时,内部类是不加载的,只有当方法被调用才会调用内部类,实例化单例
3. 反射破坏单例
上述代码貌似很牛皮,下面用反射方式破坏上述懒汉式单例模式
public static void main(String[] args) throws Exception {
Class<?> clazz = LazyInnerClassSingleton.class;
//反射获取构造器
Constructor constructor = clazz.getDeclaredConstructor(null);
//强制访问
constructor.setAccessible(true);
//暴力初始化两次,相当于new了两次
Object object1 = constructor.newInstance();
Object object2 = constructor.newInstance();
System.out.println(object1==object2);
}
}
结果为false,并没能保证单例模式
原因可见构造这边出了问题,对构造器进行优化
public class LazyInnerClassSingleton {
public LazyInnerClassSingleton(){
if (LazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例!");
}
}
public static final LazyInnerClassSingleton getInstance(){
return null;
}
private static class LazyHolder{
public static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
在运行上面的main方法,结果如下
此时,内部类方式的单例模式大功告成
你以为到此结束了么,不还有
4.序列化破坏单例
- 一个单例对象创建好后,若将对象序列化写入磁盘,下次使用再取出来反序列化,将其转化为内存对象,反序列化的对象会重新分配内存,即重新创建,违背了单例模式的初衷
代码复现上述问题
public class SerializableSingleton implements Serializable {
public final static SerializableSingleton instance = new SerializableSingleton();
public static SerializableSingleton getInstance(){
return instance;
}
}
测试代码:
public static void main(String[] args) throws Exception{
SerializableSingleton s1 =null;
SerializableSingleton s2 = SerializableSingleton.getInstance();
//写
FileOutputStream fileOutputStream = null;
fileOutputStream = new FileOutputStream("SerializableSingleton.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(s2);
objectOutputStream.flush();
objectOutputStream.close();
//读
FileInputStream fileInputStream = new FileInputStream("SerializableSingleton.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
s1 = (SerializableSingleton) objectInputStream.readObject();
objectInputStream.close();
//比较
System.out.println(s1.toString());
System.out.println(s2.toString());
System.out.println(s1==s2);
}
结果:
注:序列化就是把内存中的状态通过转换成字节码形式,从而转换成一个I/O流,写入到其他地方(磁盘,网络I/O),内存中的状态会永久保存
反序列化,将已经持久化的字节码内容转换成I/O流,通过I/O流读取,进而将读取的内容转换成java对象,在转换过程中会重新创建对象
解决方式:修改SerializableSingleton (添加readResolve())
public class SerializableSingleton implements Serializable {
public final static SerializableSingleton instance = new SerializableSingleton();
public static SerializableSingleton getInstance(){
return instance;
}
private Object readResolve(){
return instance;
}
}
再次测试上述代码,结果如下:
为什么?
我也不知道,一起来看一下源码
ObjectInputStream==>readObject() 调用了readObject0(false)方法
Object obj = readObject0(false);
进入readObject0( )方法==>readOrdinaryObject(unshared)
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
readOrdinaryObject()中,调用isInstantiable()方法,判断一下构造函数是否不为null,意味着只要有无参构造方法就会被实例化(一个类默认有一个无参构造方法,无覆盖前提下)
继续往下走:
readOrdinaryObject()调用isInstantiable()方法后,又调用了hasReadResolveMethod()
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
这个方法很简单,判断readResolveMethod 是否为null,在 ObjectStreamClass类中,私有ObjectStreamClass方法,对参数readResolveMethod 赋值
private ObjectStreamClass(final Class<?> cl) {
...
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
...
}
上面逻辑就是通过反射找到一个无参的readResolve()方法,并保存起来
如果hasReadResolveMethod方法返回true,调用invokeReadResolve()方法,利用反射调用invokeReadResolve()方法
return readResolveMethod.invoke(obj, (Object[]) null);
总结:虽然通过增加了readResolve()方法,解决了单例被破坏的问题,但实际上还是实例化了两次,只不过新创建的对象没有被返回而已
针对上述问题进一步改进
5. 注册式单例模式
5.1 枚举式单例模式
- 直接上代码
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
- 测试
public static void main(String[] args) throws Exception{
EnumSingleton instance1 =null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setData(new Object());
//写
FileOutputStream fileOutputStream = null;
fileOutputStream = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance2);
objectOutputStream.flush();
objectOutputStream.close();
//读
FileInputStream fileInputStream = new FileInputStream("EnumSingleton.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
instance1 = (EnumSingleton) objectInputStream.readObject();
objectInputStream.close();
//比较
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData()==instance2.getData());
}
- 结果:
- 枚举式单例模式在静态代码块中就给 instance 进行了赋值,是饿汉式单例模式的实现
static{
INSTANCE = new EnumSingleton("INSTANCE",0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
- 能不能通过序列化破坏呢?
答案是不会的,在readObject0()中,查看readEnum()方法,会发现枚举类型是通过类名和类对象找到一个唯一的枚举对象,枚举对象不可能被类加载器加载多次
case TC_ENUM:
return checkResolve(readEnum(unshared));
- 能不能通过反射破坏呢?
答案是不会的
测试代码:
public static void main(String[] args) throws Exception {
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.newInstance();
}
结果:(没有找到构造函数)
打开源码就能发现,Java.lang.Enum中只有一个构造方法,没有空参构造器
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
那么尝试一下通过用这个构造器反射
测试代码:
public static void main(String[] args) throws Exception{
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingleton enumSingleton = (EnumSingleton) constructor.newInstance("Tom",666);
}
结果跟猜测一样(无法通过反射创建枚举对象)
原因是什么呢,进入Constructor中的newInstance()方法中,发现如下代码,意思就是
如果修饰符是Modifier.ENUM,直接抛出异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
5.2 容器式单例
直接上代码
public class ContainerSingleton {
public static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getBean(String className){
synchronized (ioc){
if (!ioc.containsKey(className)) {
Object object= null;
try {
object = Class.forName(className).newInstance();
ioc.put(className,object);
} catch (Exception e) {
e.printStackTrace();
}
return object;
} else {
return ioc.get(className);
}
}
}
}
容器式单例模式适用于实例非常多的情况,便于管理,属于非线程安全的
总结: