什么是单例?
确保一个类在任何情况下(任何情况:多线程、并发、反射调用构造器)只有一个实例,并提供一个全局访问点。单例会因此其所有的方法、属于创建型模式。
单例的几种写法?
懒汉、饿汉、枚举、double check 、内部类。
一、饿汉式:不管用不用,都先赋值。优点:线程安全、逻辑简单、空间换时间;缺点:如果这种写法被大量使用,导致内存开销增加。
二、懒汉式:即用即赋值,不调用不赋值。第一次使用时先判断,有没有,没有就创建,第二次时就直接返回。优点:资源占用少、逻辑简单;缺点:线程不安全。
三、简单写法(synchronized加在方法上,锁的是整个类):
缺点:性能低,因为阻塞,线程始终处于等待。
四、双重验证写法(synchronized锁加在方法内,在两次判断之间):
缺点:可读性下降,不够优雅。
五、LazyInnerClassSingleton:
优点:代码优雅,利用天生的语法优势
以上5种写法共同缺点:
private Singleton(){}是一个假象,使用暴力反射,getDeclered方法后,会获取到对象,在new Instance(),会得到多个对象,违背单例。
解决方法:
六、1.构造方法中加上一个if判断,判断Lazy类不为空,为空时,抛出运行时异常;
六、2.注册式单例(容器式注册、枚举式注册):
- 枚举式注册基于key-value形式,每个key对应一个实例,被加载前就已经被赋值,赋值可以做成动态的,类似懒汉式,底层实际还是通过volatile进行修饰。通过暴力反射时获取不到,因为枚举式注册,没有无参构造。
七、Spring Ioc容器就是单例的实现,容器式单例:
public class ContainerSingleton { `` private ContainerSingleton(){} private static Map<String , Object> map=new HashMap<>(); public static void putInstance(String key, Object value){ if (key!=null && !key.isEmpty() && value!=null){ if (!map.containsKey(key)){ map.put(key,value); } } } public static Object getInstance(String key){ return map.get(key); } }
但是HashMap线程不安全,使用HashTable会影响性能,忽略反射、序列化等问题,可以使用。
代码引用于:https://blog.csdn.net/zhangxiangliang2/article/details/91896101
八、ThreadLocal单例:
线程局部变量,是一种多线程间并发访问变量的一种解决方案,与其synchronized加锁的方式不同,它完全不提供锁,以空间换时间,多线程下为每个线程提供实例,以保障线程安全,不能保证程序唯一,但是能保证线程唯一,并且每个线程中拿到的实例时同一个,不同线程中拿到的不同。
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstance= new ThreadLocal<ThreadLocalInstance>(){ @Override
protected ThreadLocalInstance initialValue() { return new ThreadLocalInstance();
} };
private ThreadLocalInstance(){}
public static ThreadLocalInstance getInstance(){
return threadLocalInstance.get();
}
}
原文链接:https://blog.csdn.net/zhangxiangliang2/article/details/91896101
- 两个线程打印相同结果:
1.线程按顺序执行(正常);
2.线程同时进入,后者覆盖前者。
- 两个线程打印不同结果:
1.线程同时进入(先后执行之后的逻辑)。