当前位置: 首页 > news >正文

响应式网站开发流程/合肥seo推广公司

响应式网站开发流程,合肥seo推广公司,云匠网接单,住房建设局网站单例模式 有些对象我们只需要一个,比如线程池、ServletContext、ApplicationContext、 Windows中的回收站,此时我们便可以用到单例模式。 单例模式就是确保一个类在任何情况下都只有一个实例,并提供一个全局访问点。 1. 饿汉式单例 /*** …

单例模式

有些对象我们只需要一个,比如线程池、ServletContext、ApplicationContext、 Windows中的回收站,此时我们便可以用到单例模式。

单例模式就是确保一个类在任何情况下只有一个实例,并提供一个全局访问点

1. 饿汉式单例

/*** @author 蝉沐风* 饿汉式单例*/
public class HungrySingleton {//类初始化的时候便进行对象实例化private static final HungrySingleton hungrySingleton = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance() {return hungrySingleton;}
}

优点:

  • 饿汉式单例是最简单的一种单例形式,它没有添加任何的锁,执行效率最高
  • 线程安全

缺点:

某些情况下,造成内存浪费,因为对象未被使用的情况下就会被初始化,如果一个项目中的类多达上千个,在项目启动的时候便开始初始化可能并不是我们想要的。

2. 简单的懒汉式单例

想解决饿汉式单例一开始就会进行对象的初始化的问题,一个很自然的想法就是当用户调用getInstance方法的时候再进行实例的创建,修改代码如下:

/*** @author 蝉沐风* 饿汉式单例*/
public class LazySimpleSingleton {private static LazySimpleSingleton instance;private LazySimpleSingleton() {}public static LazySimpleSingleton getInstance() {// 如果实例不存在,则进行初始化if (instance == null) {instance = new LazySimpleSingleton();}return instance;}
}

上述代码在单线程下能够完美运行,但是在多线程下存在安全隐患。大家可以使用IDEA进行手动控制线程执行顺序来跟踪内存变化,下面我用图解的形式进行多线程下3种情形的说明。

情形1:

image-20211013230606616 每个线程依次执行getInstance方法,得到的结果正是我们所期望的

情形2:

image-20211013234035710

此种情形下,该种写法的单例模式会出现多线程安全问题,得到两个完全不同的对象

情形3:

image-20211013233556007

该种情形下,虽然表面上最终得到的对象是同一个,但是在底层上其实是生成了2个对象,只不过是后者覆盖了前者,不符合单例模式绝对只有一个实例的要求。

3. 升级的懒汉式单例

/*** @author 蝉沐风* 饿汉式单例-同步锁*/
public class LazySynchronizedSingleton {private static LazySynchronizedSingleton instance;private LazySynchronizedSingleton() {}//添加synchronized关键字public synchronized static LazySynchronizedSingleton getInstance() {if (instance == null) {instance = new LazySynchronizedSingleton();}return instance;}
}

升级之后的程序能完美地解决线程安全问题。

但是用synchronized加锁时,在线程数量较多的情况下,会导致大批线程阻塞,从而导致程序性能大幅下降

有没有一种形式,既能兼顾线程安全又能提升程序性能呢?有,这就是双重检查锁。

4. 双重检查锁

/*** @author 蝉沐风* 双重检查锁*/
public class LazyDoubleCheck {// 需要添加 volatile 关键字private volatile static LazyDoubleCheck instance;private LazyDoubleCheck() {}public static LazyDoubleCheck getInstance() {//一重检查:检查实例,如果不存在,进入同步区块if (instance == null) {synchronized (LazyDoubleCheck.class) {//双重检查:进入同步区块后,再检查一次,如果仍然是null,才创建实例if (instance == null) {instance = new LazyDoubleCheck();}}}return instance;}
}

第一重检查是为了确认instance是否已经被实例化,如果是,则无需再进入同步代码块,直接返回实例化对象,否则进入同步代码块进行创建,避免每次都排队进入同步代码块影响效率;

第二重检查是真正与实例的创建相关,如果instance未被实例化,则在此过程中被实例化。

双重检查锁版本的单例模式需要使用到volatile关键字,本文不对volatile关键字进行深入分析,之后会单独开一篇文章进行解释

但是,使用synchronized关键总归是要上锁的,对程序性能还是存在影响,下面介绍一种利用Java本身语法特性来实现的一种单例写法。

5. 静态内部类实现单例

/*** @author 蝉沐风* 静态内部类实现单例*/
public class LazyStaticInnerClassSingleton {private LazyStaticInnerClassSingleton() {}public static final LazyStaticInnerClassSingleton getInstance() {return LazyHolder.LAZY;}// 静态内部类,未被使用时,是不会被加载的private static class LazyHolder {private static final LazyStaticInnerClassSingleton LAZY = new LazyStaticInnerClassSingleton();}
}

用静态内部类实现的单例本质上是一种懒汉式,因为在执行getInstance中的LazyHolder.LAZY语句之前,静态内部类并不会被加载。

这种方式既避免了饿汉式单例的内存浪费问题,又摆脱了synchronized关键字的性能问题,同时也不存在线程安全问题。


到此为止,我们介绍了5种单例写法(除去简单的懒汉式单例由于多线程问题无法用于生产中,其实只有4种),我们发现上述单例模式本质上都是将构造方法私有化,避免外部程序直接进行实例化来达到单例的目的。

那如果我们能够想办法获取到类的构造方法,或者将创建好的对象写入磁盘,然后多次加载到内存,是不是可以破坏上述所有的单例呢?

答案是肯定的,下面我们用反射序列化两种方法亲自毁灭我们一手搭建的单例。

6. 反射破坏单例

/*** @author 蝉沐风* 利用反射破坏单例*/
public class SingletonBrokenByReflect {public static void main(String[] args) {try {Class<?> clazz = LazyStaticInnerClassSingleton.class;//通过反射弧获取类的私有构造方法Constructor c = clazz.getDeclaredConstructor(null);//强制访问c.setAccessible(true);Object obj1 = c.newInstance();Object obj2 = c.newInstance();//输出falseSystem.out.println(obj1 == obj2);} catch (Exception e) {e.printStackTrace();}}}

如此,我们便使用反射破坏了单例。现在我们以静态内部类单例为例,解决这个问题。

我们在构造方法中添加一些限制,一旦检测到对象已经被实例化,但是构造方法仍然被调用时直接抛出异常。

/*** @author 蝉沐风* 静态内部类实现单例*/
public class LazyStaticInnerClassSingleton {private LazyStaticInnerClassSingleton() {if (LazyHolder.LAZY != null) {throw new RuntimeException("实例被重复创建");}}public static final LazyStaticInnerClassSingleton getInstance() {return LazyHolder.LAZY;}// 静态内部类,未被使用时,是不会被加载的private static class LazyHolder {private static final LazyStaticInnerClassSingleton LAZY = new LazyStaticInnerClassSingleton();}
}

7. 序列化破坏单例

单例对象创建好之后,有时需要将对象序列化然后写入磁盘,在需要时从磁盘中读取对象并加载至内存,反序列化后的对象会重新分配内存,如果序列化的目标对象恰好是单例对象,就会破坏单例模式。

/*** @author 蝉沐风* 可序列化的单例*/
public class SeriableSingleton implements Serializable {//类初始化的时候便进行对象实例化private static final SeriableSingleton hungrySingleton = new SeriableSingleton();private SeriableSingleton() {}public static SeriableSingleton getInstance() {return hungrySingleton;}
}/*** @author 蝉沐风* 序列化破坏单例*/
public class SingletonBrokenBySerializing {public static void main(String[] args) {SeriableSingleton s1 = SeriableSingleton.getInstance();SeriableSingleton s2 = null;FileOutputStream fos = null;try {File file;fos = new FileOutputStream("SeriableSingleton.obj");OutputStream out;ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s1);oos.flush();oos.close();fos.close();FileInputStream fis = new FileInputStream("SeriableSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s2 = (SeriableSingleton) ois.readObject();ois.close();fis.close();//输出为falseSystem.out.println(s1 == s2);} catch (Exception e) {}}}

从运行结果上看,反序列化和手动创建出来的对象是不一致的,违反了单例模式的初衷。

那到底如何保证在序列化的情况下也能够实现单例模式呢,其实很简单,只需要增加一个readResolve方法即可。

public class SeriableSingleton implements Serializable {//类初始化的时候便进行对象实例化private static final SeriableSingleton hungrySingleton = new SeriableSingleton();private SeriableSingleton() {}public static SeriableSingleton getInstance() {return hungrySingleton;}//只需要添加这一个函数即可private Object readResolve() {return hungrySingleton;}
}

实现的原理涉及到ObjectInputStream的源码,不属于本文的研究重点,如果读者需要,我可以另开一篇来进行讲解。

8. 注册式单例模式

8.1 枚举式单例模式

很多博客和文章的实现方式如下(文件名:EnumSingleObject.java)

/** @author 蝉沐风* 枚举式单例1*/
public class EnumSingleObject {private EnumSingleObject() {}enum SingletonEnum {INSTANCE;private EnumSingleObject instance;private SingletonEnum() {instance = new EnumSingleObject();}public EnumSingleObject getInstance() {return INSTANCE.instance;}}//对外暴露一个获取EnumSingleObject对象的静态方法public static EnumSingleObject getInstance() {return SingletonEnum.INSTANCE.getInstance();}
}

枚举式的写法为什么可以实现我们的单例模式呢,我们首先使用javac EnumSingleObject.java生成EnumSingleObject.class文件,用反编译工具Jad在.class所在的目录下执行 jad EnumSingleObject.class命令,得到EnumSingleObject.jad文件,代码如下

static final class EnumSingleObject$SingletonEnum extends Enum {public static EnumSingleObject$SingletonEnum[] values() {return (EnumSingleObject$SingletonEnum[]) $VALUES.clone();}public static EnumSingleObject$SingletonEnum valueOf(String s) {return (EnumSingleObject$SingletonEnum) Enum.valueOf(com / chanmufeng / Singleton / registerSingleton / EnumSingleObject$SingletonEnum, s);}public EnumSingleObject getInstance() {return INSTANCE.instance;}public static final EnumSingleObject$SingletonEnum INSTANCE;private EnumSingleObject instance;private static final EnumSingleObject$SingletonEnum $VALUES[];// 该static代码块是枚举写法能够实现单例模式的关键static {INSTANCE = new EnumSingleObject$SingletonEnum("INSTANCE", 0);$VALUES = (new EnumSingleObject$SingletonEnum[]{INSTANCE});}private EnumSingleObject$SingletonEnum(String s, int i) {super(s, i);instance = new EnumSingleObject();}
}

其实,枚举式单例在静态代码块中就为INSTANCE进行了赋值,是一种饿汉式单例模式的体现,只不过这种饿汉式是JDK底层为我们做的操作,我们只是利用了JDK语法的特性罢了。

序列化能否破坏枚举式单例

//测试序列化能否破坏
public static void main(String[] args) {EnumSingleObject s1 = EnumSingleObject.getInstance();EnumSingleObject s2 = null;FileOutputStream fos = null;try {fos = new FileOutputStream("SeriableSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s1);oos.flush();oos.close();fos.close();FileInputStream fis = new FileInputStream("SeriableSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s2 = (EnumSingleObject) ois.readObject();ois.close();fis.close();//输出为falseSystem.out.println(s1 == s2);} catch (Exception e) {e.printStackTrace();}}

很遗憾,序列化依然会破坏枚举式单例EnumSingleObject

What???不是说枚举式单例非常的优雅吗?连Effective Java都推荐使用吗?

别急,接下来我们观察另一种写法

/*** @author 蝉沐风* 枚举式单例2*/
public enum EnumSingleObject2 {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnumSingleObject2 getInstance() {return INSTANCE;}
}

我们再来进行序列化测试

public static void main(String[] args) {EnumSingleObject2 s1 = EnumSingleObject2.getInstance();s1.setData(new Object());EnumSingleObject2 s2 = null;FileOutputStream fos = null;try {fos = new FileOutputStream("SeriableSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s1);oos.flush();oos.close();fos.close();FileInputStream fis = new FileInputStream("SeriableSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s2 = (EnumSingleObject2) ois.readObject();ois.close();fis.close();//输出为trueSystem.out.println(s1 == s2);} catch (Exception e) {e.printStackTrace();}}

打印结果为true,说明枚举式单例2的写法可以防止序列化破坏。

而很多文章和博客用的往往是第1种写法,下面我们解释这两种写法的区别.

我们进入ObjectInputStream类的readObject()方法

public final Object readObject()throws IOException, ClassNotFoundException{...try {Object obj = readObject0(false);...return obj;} finally {...}}

readObject()方法中又调用了readObject0()方法

private Object readObject0(boolean unshared) throws IOException {...//枚举式单例1的程序会进入到这里case TC_CLASS:return readClass(unshared);...//枚举式单例2的程序会进入到这里case TC_ENUM:return checkResolve(readEnum(unshared));}

我们先看一下readEnum()方法

private Enum<?> readEnum(boolean unshared) throws IOException {...String name = readString(false);Enum<?> result = null;Class<?> cl = desc.forClass();if (cl != null) {try {@SuppressWarnings("unchecked")//!!!!这里是重点Enum<?> en = Enum.valueOf((Class)cl, name);result = en;} catch (IllegalArgumentException ex) {...}}...return result;}

到这里我们发现,枚举类型其实通过类名和类对象找到唯一一个枚举对象,因此,枚举对象不会被类加载器加载多次。

readClass()并无此功能。

反射能否破坏枚举式单例

public static void main(String[] args) {try {Class<?> clazz = EnumSingleObject2.class;//通过反射获取类的私有构造方法Constructor c = clazz.getDeclaredConstructor(null);//强制访问c.setAccessible(true);Object obj1 = c.newInstance();Object obj2 = c.newInstance();//输出falseSystem.out.println(obj1 == obj2);} catch (Exception e) {e.printStackTrace();}}

运行结果如下

image-20211024210253823

结果报了java.lang.NoSuchMethodException异常,原因是java.lang.Enum中没有无参的构造方法,我们查看java.lang.Enum的源码,只有下面一个构造函数

protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;
}

我们改变一下反射构建的方式

public static void main(String[] args) {try {Class<?> clazz = EnumSingleObject2.class;//通过反射获取类的私有构造方法
//            Constructor c = clazz.getDeclaredConstructor(null);Constructor c = clazz.getDeclaredConstructor(String.class, int.class);//强制访问c.setAccessible(true);Object obj1 = c.newInstance();Object obj2 = c.newInstance();//输出falseSystem.out.println(obj1 == obj2);} catch (Exception e) {e.printStackTrace();}}

运行结果如下

image-20211024212215573

Cannot reflectively create enum objects,即不能用反射来创建枚举对象,这是ConstructornewInstance()方法在源码上决定的,继续看

public T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{...if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor;   // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}

从源码中可以看出,newInstance()方法中做了强制性的判断,如果修饰符是Modifier.ENUM类型,则直接抛出异常。

最后介绍一种注册时单例的另一种写法:容器式单例

8.2 容器式单例模式

/*** @author 蝉沐风* 容器式单例*/
public class 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中就使用了该种单例模式。

总结

单例模式可以保证内存中任何情况下只有一个实例,是最简单的一种设计模式,实现起来也很简单,但是实现方式比较多,涉及到的小细节也比较多,在面试中是一个高频面试点。

我是蝉沐风,那些看似微不足道的坚持,会突然在某一天让你看到它的意义,欢迎大家留言!

微信公众号结尾宣传图片

http://www.lbrq.cn/news/1373509.html

相关文章:

  • 廊坊商昊网站建设/外贸全网营销推广
  • 反馈网站制作/公司网络营销策划书
  • 用vs2010做网站导航/天津seo托管
  • 广告营销方式/seo优化按天扣费
  • 政府网站建设年度报告/百度网盘官网登录入口
  • iis 建网站手机访问/网络营销技巧培训班
  • 网站建设做的人多吗/广州疫情最新消息
  • wp企业网站模板/seo怎么优化软件
  • 红色餐饮网站源码/腾讯营销平台
  • 旅游网站建设色彩搭配表/和生活爱辽宁免费下载安装
  • 女人动漫做受网站/图片外链生成
  • 建域名做网站/湖南优化电商服务有限公司
  • 做设计那些网站可以卖设计/宁波seo优化公司排名
  • 南昌手机网站制作/自己个人怎样做电商
  • 做的最好的本地生活网站/泉州百度seo公司
  • 攻略类型网站如何做产品营销/seo优化网站优化排名
  • 网课培训班/关键词优化外包
  • 常见的网址有哪些/安徽seo优化规则
  • php做网站安性如何/网站优化检测
  • 郑州做旅游网站/绍兴seo推广
  • 网站建设服务案例/网站seo推广员招聘
  • 金融投资网站开发/南昌网站优化公司
  • 中山网站建设制作/济南网络优化哪家专业
  • 研磨材料 东莞网站建设/重庆seo网站管理
  • 简单的旅游网站怎么做/搜索排名怎么做
  • 企业网页制作软件/公众号排名优化软件
  • 做公众号的网站模板下载吗/广州网站优化外包
  • 湖北省建设厅七大员报名网站/百度快照官网
  • c2c商城网站建设费用/网络市场调研的方法
  • 精品网站制作公司/微信小程序官网
  • 【超分辨率专题】PiSA-SR:单步Diff超分新突破,即快又好,还能在线调参
  • 基于MBA与BP神经网络分类模型的特征选择方法研究(Python实现)
  • 通俗易懂解释Java8 HashMap
  • 涉水救援机器人cad【12张】三维图+设计书明说
  • K8S部署ELK(三):部署Elasticsearch搜索引擎
  • Typora v1.10.8 好用的 Markdown 编辑器