单例模式
说明:
单例模式(Singleton Pattern)属于创建型模式,是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
在某些业务场景中某个对象只需要一个实例即可,比如Context上下文、Spring中的Ioc容器,一般来说工厂类也会设置成单例的。
常见写法
1. 饿汉式单例
饿汉式单例在单例类加载时就创建了实例,如果这种单例类对象比较多,就会造成内存空间的浪费。
private static final HungrySingleton hungerSingleton = new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return hungerSingleton;
}
2.懒汉式单例
懒汉式单例在被调用时才会实例化,不会造成内存空间的浪费,但是需要保证线程安全问题(双重检查锁)。
/**
* 懒汉式单例_双重检查锁
*/
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){};
/**
* 将synchronized关键字放在方法域里面,避免将整个类锁死
* @return
*/
public static LazyDoubleCheckSingleton getInstance(){
if (lazy == null){
synchronized(LazyDoubleCheckSingleton.class){
// 双重检查锁,避免多线程重新new对象
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();
// CPU执行时会转换成JVM指令执行
// 指定重新排序问题,volatile
// 1、分配内存给这个对象
// 2、初始化对象
// 3、将初始化的对象和内存地址建立连接,赋值
// 4、用户初次访问
}
}
}
return lazy;
}
}
静态内部类懒汉式
/**
* 懒汉式单例_内部类
* 没有用到synchronized关键字
* 性能最优的一种写法
*/
public class LazyInnerClassSingleton {
// 虽然构造方法私有了,但是逃不过反射的法眼
private LazyInnerClassSingleton(){
// 添加判断阻止反射机制破坏单例模式
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许构建多个实例!");
}
};
/**
* LazyHolder里面的逻辑需要等到外部方法调用时才执行
* 巧妙的利用了内部类的特性
* JVM底层执行逻辑,完美地避免了线程安全问题
* @return
*/
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
3.注册式单例(枚举式、容器式)
将每一个实例都缓存到统一的容器中,使用唯一标识获取实例。
/**
* 注册式单例_枚举型
* 枚举型单例虽然也是属于饿汉式单例的一种,不可避免的也会造成资源浪费
* 不过相对于普通的饿汉式单例性能会更优一些,因为JDK底层对枚举型单例做了更多的优化
*/
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 class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
/**
* 对象方便管理,其实也属于懒加载
* 存在线程安全问题,所以需要加上synchronized关键字
* @param className
* @return
*/
public static Object getBeam(String className){
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
return ioc.get(className);
}
}
}
ThreadLocal单例
保证线程内部的全局唯一,且天生线程安全。
/**
* TreadLocal单例魔兽
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue(){
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
防止序列化破坏单例
/**
* 饿汉式单例
* 使用反序列化破坏单例模式
*/
public class SeriableSingleton implements Serializable {
// 序列化就是说把内存中的状态通过转换成字节码的形式
// 从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
// 内存中状态给永久保存下来
// 反序列化
// 将已经持久化的字节码内容,转换为IO流
// 通过IO流的读取,进而将读取的内容转换为Java对象
// 在转换过程中会重新创建对象new
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
// 加上该方法,避免反序列化破坏单例
// 重写该方法,只不过覆盖了反序列化出来的对象
// 还是创建了两次,发生在JVM层面,相对来说比较安全
// 之前反序列化出来的对象会被GC回收
private Object readResolve(){return INSTANCE;}
}