懒汉式单例的特点是:被外部类调用的时候内部类才会加载
示例1(普通写法)
package com.zwx.design.pattern.singleton.lazy;
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance(){
if(null == lazySingleton){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
上面的写法是最简单的一种懒汉式单例写法,但是存在线程安全问题,多线程情况下会有一定几率返回多个单例对象,这明显违背了单例对象原则,那么如何优化上面的代码呢?答案就是加上synchronized关键字
示例2(synchronized写法)
package com.zwx.design.pattern.singleton.lazy;
import com.zwx.design.pattern.singleton.hungry.HungrySingleton;
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance(){
if(null == lazySingleton){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
示例2的写法仅仅是在getInstance()方法上面加了synchronized关键字,其他地方没有任何变化。用 synchronized 加锁,在线程数量比较多情况下,如果CPU分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。那么,有没有一种更好的方式,既兼顾线程安全又提升程序性能呢?答案是肯定的。接下来就在介绍一种双重检查锁(double-checked locking)单例写法
示例3(DCL写法)
package com.zwx.design.pattern.singleton.lazy;
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazySingleton = null;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance(){
if(null == lazySingleton){//1
synchronized (LazyDoubleCheckSingleton.class){//2
if(null == lazySingleton){//3
lazySingleton = new LazyDoubleCheckSingleton();//4
}
}
}
return lazySingleton;//5
}
}
这里的写法将同步放在了方法里面的第一个非空判断之后,这样可以确保对象不为空的时候不会被阻塞,但是第二个非空判断的意义是什么呢?我们假设线程A首先获得锁,进入了第3行,还没有释放锁的时候,线程B又进来了,这时候因为线程还没有执行对象初始化,所以判空成立,会进入第2行等待获得锁,这时候当线程A释放锁之后,线程B会进入到第3行,这时候因为第二个判空判断对象不为空了,所以就会直接返回,如果没有第2个判空,这时候就会产生新的对象了,所以需要两次判空!
大家可能注意到这里的变量定义上加了volatile关键字,为什么呢?这是因为DCL在可能会存在失效的情况:
第4行代码:lazySingleton = new LazyDoubleCheckSingleton();
大致存在以下三步:
(1)、分配内存给对象
(2)、初始化对象
(3)、将初始化好的对象和内存地址建立关联(赋值)
而这3步由于CPU指令重排序,不能保证一定按顺序执行,假如线程A正在执行new的操作,第1步和第3步都执行完了,但是第2步还没执行完,这时候线程B进入到方法中的第1行代码,判空不成立,所以直接返回了对象,而这时候对象并没有初始化完全,所以就会报错了,解决这个问题的办法就是使用volatile关键字,禁止指令重排序(jdk1.5之后),保证按顺序执行上面的三个步骤。想要详细了解volatile关键字是如何解决重排序问题的,可以点击这里。
示例4(内部类写法)
package com.zwx.design.pattern.singleton.lazy;
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){
}
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
上面的写法巧妙的利用了内部类的特性,LazyHolder里面的逻辑需要等到外面方法调用时才执行。
这种写法看起来很完美,没有加锁,也保证了懒加载,但是这种单例模式也有问题,那就是可以被反射或者序列化破坏单例,下面我们写一个反射破坏单例的例子
package com.zwx.design.pattern.singleton.lazy;
import java.lang.reflect.Constructor;
public class LazyInnerClassSingletonTest {
public static void main(String[] args) throws Exception {
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Object o1 = constructor.newInstance();
Object o2 = LazyInnerClassSingleton.getInstance();
System.out.println(o1 == o2);//false
}
}
上面这个结果输出的结果为false,说明产生了2个对象,当然,要防止反射破坏单例很简单,我们可以把上面例子中的构造方法加一个判断就可以了:
private LazyInnerClassSingleton(){
//防止反射攻击
if(null != LazyHolder.LAZY){
throw new RuntimeException(“不允许构造多个实例”);
}
}
这样虽然防止了反射破坏单例,但是依然可以被序列化破坏单例,下面就让我们验证一下序列化是如何破坏单例的!
首先对上面的类实现序列化接口
public class LazyInnerClassSingleton implements Serializable
接下来开始对单例对象类进行序列化和反序列化测试:
package com.zwx.design.pattern.singleton.lazy;
import com.zwx.design.pattern.singleton.seriable.SeriableSingleton;
import java.io.*;
import java.lang.reflect.Constructor;
public class LazyInnerClassSingletonTest {
public static void main(String[] args) throws Exception {
LazyInnerClassSingleton s1 = null;
LazyInnerClassSingleton s2 = LazyInnerClassSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(“LazyInnerClassSingleton.obj”);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(“LazyInnerClassSingleton.obj”);
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (LazyInnerClassSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);//false
}catch (Exception e){
e.printStackTrace();
}
}
}
这时候输出结果为false,说明产生了2个对象,那么我们应该如何防止序列化破坏单例呢?我们可以对LazyInnerClassSingleton类加上readResolve方法就可以防止序列化破坏单例
package com.zwx.design.pattern.singleton.lazy;
import java.io.Serializable;
public class LazyInnerClassSingleton implements Serializable {
private LazyInnerClassSingleton(){
//防止反射攻击
if(null != LazyHolder.LAZY){
throw new RuntimeException(“不允许构造多个实例”);
}
}
//防止序列化破坏单例
private Object readResolve(){
return LazyHolder.LAZY;
}
public static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
这是因为JDK源码中会检验一个类中是否存在一个readResolve()方法,如果存在,则会放弃通过序列化产生的对象,而返回原本的对象,也就是说,在校验是否存在readResolve()方法前产生了一个对象,只不过这个对象会在发现类中存在readResolve()方法后丢掉,然后返回原本的单例对象,保证了单例的唯一性,这种写法虽然保证了单例唯一,但是过程中类也是会被实例化两次,假如创建对象的频率增大,就意味着内存分配的开销也随之增大,那么有没有办法从根本上解决问题呢?那么下面就让继续介绍一下注册式单例
注册式单例就是将每一个实例都保存到某一个地方,然后使用唯一的标识获取实例
示例1(容器式)
package com.zwx.design.pattern.singleton.register;
public class ContainerSingleton {
private ContainerSingleton(){
}
private static Map<String,Object> ioc = new ConcurrentHashMap<>();
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;
}
return ioc.get(className);
}
}
}
容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的,spring中的单例就是属于此种写法
示例2(枚举式)
package com.zwx.design.pattern.singleton.register;
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;
}
}
枚举式单例是《Effective java》一书中推荐的写法,这种写法避免了上面的内部类写法中存在的问题(虽然结果唯一,但是过程产生了多个实例对象),是一种效率较高的写法
ThreadLocal不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全
示例
package com.zwx.design.pattern.singleton.threadlocal;
public class ThreadLocalSingleton {
private ThreadLocalSingleton() {
}
private static final ThreadLocal singleton =
new ThreadLocal() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getInstance(){
return singleton.get();
}
}
测试
package com.zwx.design.pattern.singleton.threadlocal;
import com.zwx.design.pattern.singleton.ExectorThread;
import com.zwx.design.pattern.singleton.ExectorThread3;
public class ThreadLocalSingletonTest {
分享
这次面试我也做了一些总结,确实还有很多要学的东西。相关面试题也做了整理,可以分享给大家,了解一下面试真题,想进大厂的或者想跳槽的小伙伴不妨好好利用时间来学习。学习的脚步一定不能停止!
Spring Cloud实战
Spring Boot实战
面试题整理(性能优化+微服务+并发编程+开源框架+分布式)
}
};
public static ThreadLocalSingleton getInstance(){
return singleton.get();
}
}
测试
package com.zwx.design.pattern.singleton.threadlocal;
import com.zwx.design.pattern.singleton.ExectorThread;
import com.zwx.design.pattern.singleton.ExectorThread3;
public class ThreadLocalSingletonTest {
分享
这次面试我也做了一些总结,确实还有很多要学的东西。相关面试题也做了整理,可以分享给大家,了解一下面试真题,想进大厂的或者想跳槽的小伙伴不妨好好利用时间来学习。学习的脚步一定不能停止!
[外链图片转存中…(img-heojU84V-1721165751635)]
Spring Cloud实战
[外链图片转存中…(img-m1pruKqS-1721165751635)]
Spring Boot实战
[外链图片转存中…(img-eIvqBB5U-1721165751636)]
面试题整理(性能优化+微服务+并发编程+开源框架+分布式)