「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
前言
- 关于作者:励志不秃头的一个CURD的Java农民工,想挑战看看自己能完成多少天的更文挑战
- 关于文章:以下内容单纯为作者了解的,如有不对,欢迎各路大神指导,下面聊聊单例模式,我还遇到过面试时要求手写单例模式,所以还是有必要了解各种写法的。马上就金三银四了,快来看看吧。
单例模式
单例模式,Singleton Pattern;是指一个类在任何情况下都绝对只有一个实例,并提供了一个全局访问带你。
单例模式是一个创建型模式
饿汉式单例
是指类加载的时候立即初始化,并且创建单例对象,是绝对线程安全的。因为在线程还没出现以前就已经实例化了
饿汉式适用在单例对象比较少的情况
public class HungrySingleton {
//写法一:
// private static HungrySingleton hungrySingleton = new HungrySingleton();
// private HungrySingleton(){}
//写法二,静态代码机制
private static HungrySingleton hungrySingleton ;
static {
hungrySingleton = new HungrySingleton();
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
懒汉式单例
- 最终版
public class LazySingleton {
private volatile static LazySingleton lazySingleton = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(lazySingleton == null){
synchronized (LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
- 为了解决线程安全,使用了 synchronized 关键字;不在 getInstance( ) 方法上加上 synchronized , 是因为在方法上加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降
- 使用 volatile 关键字修饰,是为了避免JVM底层有可能发生的重排序
- 静态内部类写法
//兼顾了饿汉式的内存浪费以及synchronized性能的问题
public class LazyInnerClassSingleton {
//默认使用 LazyInnerClassSingleton 的时候,会先初始化内部类
//但是如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton(){
//为了不被反射破坏单例
if(LazyHolder.LAZY != null){
throw new RuntimeException("非法破坏单例");
}
}
//final 关键字 ,保证该方法不会被重写、重载
//static 为了使单例的空间共享
public final static LazyInnerClassSingleton getInstance(){
//在返回结果前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
反射破坏单例
测试代码:
public class InstanceTest {
public static void main(String[] args) {
//破坏静态内部类
try{
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射拿到私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//暴力初始化
Object object1 = c.newInstance();
Object object2 = c.newInstance();
System.out.println(object1 == object2);
}catch (Exception e){
e.printStackTrace();
}
//破坏懒汉式
try{
Class<?> clazz = LazySingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object object1 = c.newInstance();
Object object2 = c.newInstance();
System.out.println(object1 == object2);
}catch (Exception e){
e.printStackTrace();
}
}
}
- 破坏静态内部类:当静态内部类的构造方法里没有加上如下判断,便会被反射破坏单例
if(LazyHolder.LAZY != null){
throw new RuntimeException("非法破坏单例");
}
- 破坏懒汉式:避免方式:
public class LazySingleton {
private volatile static LazySingleton lazySingleton = null;
private static boolean flag = false;
private LazySingleton(){
synchronized (LazySingleton.class){
if(flag == false){
flag = true;
}else {
throw new RuntimeException("非法破坏单例");
}
}
}
public static LazySingleton getInstance(){
if(lazySingleton == null){
synchronized (LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
序列化破坏单例
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();
}
}
}
得到的结果可以看出:s1和s2是两个不同的对象,违背了单例的设计原则
修改方法,只需要增加readResolve() 方法:
private Object readResolve(){
return INSTANCE;
}
这是因为JDK在底层源码设计的时候进行了避免,从源码可以看出:在 invokeReadResolve()方法中用反射调用了 readResolveMethod 方法。
虽然,增加 readResolve()方法返回实例,解决了单例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两次,只不过新创建的对象没有被返回而已
注册式单例
枚举写法
public enum EnumSingleton {
//枚举单例
INSTANCE;
private Object data;
public Object getData(){
return data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
- 枚举式单例是饿汉式单例,在静态代码代码块就对 INSTANCE 进行了赋值
- 序列化并不能破坏枚举式单例,因为在 JDK源码 中的 readEnum () 方法,通过类名和Class对象类找到一个唯一的枚举对象,所以枚举对象不可能被类加载器加载多次
- 反射不能破坏枚举式单例,会抛出异常: java.lang.NoSuchMethodException,在JDK的源码中,Constructor 的 newInstance()方法中:
在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型,直接抛出异常。
容器写法
容器写法适用于创建实例非常多的情况,便于管理,spring就是使用容器写法管理bean,非线程安全的
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String beanName){
//保证线程安全,ConcurrentHashMap只是保证了map的线程安全
synchronized (ioc){
if(!ioc.containsKey(beanName)){
Object object = null;
try{
object = Class.forName(beanName).newInstance();
ioc.put(beanName, object);
} catch (Exception e) {
e.printStackTrace();
}
return object;
}else {
return ioc.get(beanName);
}
}
}
}
ThreadLocal 线程单例
- ThreadLocal 不保证全局唯一,但是保证了在单个线程中是唯一的,天生的线程安全
- ThreadLocal 将所有的对象都放在了ThreadLocalMap 里,线程作为key,是以空间换时间来实现线程间的隔离的
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();
}
}
好了,以上就是单例模式相关的几种写法,希望对大家有一定的认识和帮助,我是新生代农民工L_Denny,我们下篇文章见。