文章目录
一、单例模式的满足条件?
1.必须一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点
2.单例模式属于创建型模式
二、饿汉式单例
1.代码
代码如下(示例):
/**
* @author zhuwenhua
* @description 饿汉式单例模式
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrystaticsingleton = new HungryStaticSingleton();
public HungryStaticSingleton getInstance(){
return hungrystaticsingleton;
}
}
2.优缺点
优点是简单效率高,没有加锁,用户体验比懒汉式好;缺点是无论用不用都会加载,浪费内存。
为了解决这个内存问题引入懒汉式单例
三、懒汉式单例
1.代码
/**
* @author zhuwenhua
* @description 懒汉式 在外部需要的时候才会实例化
*/
public class LazySimpleSington {
private static LazySimpleSington lazySimpleSington = null;
public LazySimpleSington getInstance() {
if (null == lazySimpleSington) {
return new LazySimpleSington();
}
return lazySimpleSington;
}
}
2.优缺点
乍一看在使用的时候才实例化看起来解决了内存浪费的问题,但是新的问题是多线程的情况下就不是单例的了,那么改进一下,加个锁
3.改进
代码
/**
* @author zhuwenhua
* @description 懒汉式 在外部需要的时候才会实例化
*/
public class LazySimpleSington {
private static LazySimpleSington lazySimpleSington = null;
public synchronized LazySimpleSington getInstance() {
if (null == lazySimpleSington) {
return new LazySimpleSington();
}
return lazySimpleSington;
}
}
但是这样的话每次进来都会走加锁的这个方法,效率低;再次优化一下,改成双重检测单例
4.双重检测型单例
优化之后如下
/**
* @description: 单例模式
* @author: zhuwenhua
*/
public class Singleton {
public Singleton() {
}
private static Singleton singleton;
public static Singleton getInstance(){
//检查是否要阻塞
if(null==singleton){
synchronized(Singleton.class){
//检查是否要重新创实例
if(null==singleton){
return new Singleton();
}
}
}
return singleton;
}
}
但是新的问题又来了,我们知道,在实例化的时候分为了三步
1. 分配内存给这个对象(即赋初始值,如果是int类型则给一个0)
2. 初始化对象
3. 将singleton指向这个对象
此时就存在一个指令重排序的问题,如果2和3的位置颠倒了,那么singleton指向的就是一个未初始化的对象(如果是int类型就是初始值0),就会出现问题。解决办法就是加上volatile关键字,代码如下
/**
* @description: 单例模式
* @author: zhuwenhua
*/
public class Singleton {
public Singleton() {
}
private static volatile Singleton singleton;
public static Singleton getInstance(){
//检查是否要阻塞
if(null==singleton){
synchronized(Singleton.class){
//检查是否要重新创实例
if(null==singleton){
return new Singleton();
}
}
}
return singleton;
}
}
但是吧,这样感觉写的不是很优雅,于是新一轮的优化来了
5.内部类型单例
代码如下
/**
* @author zhuwenhua
* @description
*/
public class LazyInnerClassSingleton {
//内部类单例 不使用的话就不会加载内部类
private LazyInnerClassSingleton() {
}
//static 单例空间共享
//final 以免被重写重载
public static final LazyInnerClassSingleton getInstance(){
return LazyHoder.lazyinnerclasssingleton;
}
private static class LazyHoder{
private static final LazyInnerClassSingleton lazyinnerclasssingleton = new LazyInnerClassSingleton();
}
}
四、破坏单例
1. 反射
代码如下
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Object o1 = constructor.newInstance();
Object o2 = constructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
如何解决呢,其实很简单,反射不是暴力访问无参构造方法的吗,那么我们在构造方法中判断该对象使用存在即可,如果存在就抛出异常
2.序列化破坏
SeriableSingleton s1=null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1= (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
序列化的解决办法是在该单例类中添加一个方法
/**
* 序列化破坏解决办法
*/
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton singleton =new SeriableSingleton();
public SeriableSingleton() {
}
public static SeriableSingleton getInstance(){return singleton;}
//使用了一个桥接模式
//只不过是覆盖了反序列化出来的对象
//创建了两次,之前的对象会被回收
private Object readResolve(){return singleton;}
}
3.readResolve方法原理(多图预警)
1.点击该方法往下看
2.可以看到调用了这个readObject0方法,继续往下看
3.可以看到调用这个readOrdinaryObject这个方法继续
4.此处判断是否存在构造方法,咱们的单例类是存在的,所以它调用的是desc.newInstance()这个方法,即新建了一个对象
5.我继续往下走,可以看到这个方法,点进去
6.其实就是判断这个实例是否为空,往上翻可以看到这个类其实就是
7. readResolve这个方法无参的反射
8.然后我们的单例类里边有这个方法,往下走就看到他调用了这个方法,即desc.invokeReadResolve()这个方法,反射调用咱单例类的readResolve()方法,到这里您应该就知道了为什么要写一个readResolve()就可以解决序列化破坏单例模式的问题了
那么我们有没有一种更为优雅的方式来实现呢,肯定是有的,这就是《Effective java》中极为推荐的一种方法,即枚举型单例
五、注册式单例
1. 枚举式单例
代码如下
/**
* @author zhuwenhua
* @description 枚举式单例
*/
public enum EnumSingleton {
SINGLETON;
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public static EnumSingleton getInstance(){return SINGLETON;}
}
他能不被反射破坏的原因是,如图,如果这个clazz是枚举类型的,jdk层面会帮你抛出异常。
2. 容器式单例
代码如下
/**
* @author zhuwenhua
* @description 容器式单例
*/
public class ContainerSingleton {
public ContainerSingleton() {
}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
//容器式单例线程不安全的问题可以借鉴spring中的解决方式,双重检查锁方式
public static Object getInstance(String className) throws Exception {
Object instance=null;
if(!ioc.containsKey(className)){
Object o = Class.forName(className).newInstance();
ioc.put(className,o);
return o;
}else {
return ioc.get(className);
}
}
}
其实枚举底层也是个map,这种容器式跟枚举式某种意义上来说有着异曲同工之妙,也是spring使用的方式。
六、ThreadLocal单例
代码如下
/**
* 线程内单例
*/
public class ThreadLocalSingleton {
static ThreadLocal<ThreadLocalSingleton> singleton=new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public ThreadLocalSingleton() {
}
public static ThreadLocalSingleton getInstance(){return singleton.get();};
}
其实严格意义上来说,这个并不是一种真正意义上的单例,因为他只是在一个线程中是单例,但是在某些场合依然有着举足轻重的作用