单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例
常见的单例模式:Spring框架中的ApplicationContext,数据库连接池等
饿汉式单例
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
懒汉式单例,在类加载时就创建了单例对象,绝对线程安全
优点:不需要加锁就能保证线程安全,效率高
缺点:不管有没有调用都会创建单例对象,可能造成空间的浪费
懒汉式单例
线程不安全的懒汉式
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
饿汉式单例,需要调用时才创建
这种方式无法保证线程安全,当多个线程同时访问时,可能会创建一个以上的单例对象
同步方法单例模式
我们采用synchronized关键字对方法进行修饰,使其变为同步方法
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
private static LazySimpleSingleton lazy = null;
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
通过这种方式,可以解决线程安全的问题,但是在加了锁之后,如果多线程同时调用该方法,会造成阻塞,降低执行效率
双重锁单例
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();
//1.分配内存给这个对象
//2.初始化对象
//3.设置 lazy 指向刚分配的内存地址
}
}
}
return lazy;
}
}
通过volatile关键字修饰单例对象的引用,防止创建时因为指令重排导致失败的情况
当获取的单例对象为空,需要创建单例对象时,调用同步方法创建单例对象,防止重复创建。在对象被创建了以后,调用时,不触发同步方法,直接获取单例对象,提高了效率
静态内部类单例
public class LazyInnerClassSingleton {
//默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton(){}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
这种方法效率更高,不需要使用锁,也避免了未使用就创建造成的空间浪费
当访问获取,访问时,该内部类才初始化
反射破坏单例
public class LazyInnerClassSingletonTest {
public static void main(String[] args) {
try{
//获取对象
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射拿到私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//创建对象
Object o1 = c.newInstance();
//调用了两次构造方法,相当于 new 了两次
Object o2 = c.newInstance();
System.out.println(o1 == o2);
}catch (Exception e){
e.printStackTrace();
}
}
}
为了防止被反射破坏,我们修改它的构造方法,当单例对象已经存在时,抛出异常
public class LazyInnerClassSingleton {
//默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton(){
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许创建多个实例");
}
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
序列化破坏单例
//反序列化时导致单例破坏
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;
}
}
//调用
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
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);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
反序列化后的对象和手动创建的对象是不一样的,为了防止这种情况,我们只需增加一个readResolve()方法:
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
在ObjectInputStream 类的 readObject()方法中,会判断是否有无参构造方法,如果有,则会实例化。通过反射获取readResolve()方法,返回实例,但是在该过程中,虽然返回的是一个对象,但是仍然实例化了两次
注册式单例
是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例
注册式单例有两种:一种是枚举注册,一种是容器注册
枚举单例
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>();
public static Object getBean(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;
} else {
return ioc.get(className);
}
}
}
}
这种方式可以存储多个单例对象,在Spring中就有使用容器式单例
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
/** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16);
...
}
ThreadLocal 线程单例
这种方式是一种特殊的单例模式,无法保障全局对象唯一,可以保证各个线程中的对象唯一
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();
}
}
PS:参考自咕泡学院设计模式学习笔记