- 懒汉式-方式3 (双重检查锁)
再来讨论以下懒汉模式中加锁的问题,对于 getInstace()方法来说,绝大部分的操作时读的操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的模式: 双层检查锁模式
(提升效率)
类对象加锁
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:29 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Singleton * @Description: TODO 懒汉式3 - 双层检查锁: 目的是为了提升性能 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} //2.声明Singleton变量 private static Singleton instance; //3.对外提供公共的访问方式 public static Singleton getInstance(){ //1. 第一次判断 if (instance==null){ //2. 假如为空就进入,否则直接返回。 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //3.类对象加锁... synchronized (Singleton.class){ //4.第二次判断 if (instance==null){ instance=new Singleton(); } } } return instance; } }
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:41 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { new Thread(()->{ System.out.println(Singleton.getInstance().hashCode()); }).start(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } }
双重检查锁模式是一种非常好的单列实现模式,解决了单列、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题的,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实列化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来的空指针异常问题,只需要使用Volatile
关键字,Volatile
关键字可以保证可见性和有序性。
在变量上添加关键字Volatile
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:29 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Singleton * @Description: TODO 懒汉式3 - 双层检查锁: 目的是为了提升性能 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} //2.声明Singleton变量 private static volatile Singleton instance; //*******************在变量上添加关键字 //3.对外提供公共的访问方式 public static Singleton getInstance(){ //1. 第一次判断 if (instance==null){ //2. 假如为空就进入,否则直接返回。 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //3.类对象加锁... synchronized (Singleton.class){ //4.第二次判断 if (instance==null){ instance=new Singleton(); } } } return instance; } }
package com.jsxs.pattern.singletion.demo4; /** * @Author Jsxs * @Date 2023/4/16 15:41 * @PackageName:com.jsxs.pattern.singletion.demo4 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { new Thread(()->{ System.out.println(Singleton.getInstance().hashCode()); }).start(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1.hashCode()); } }
小结: 添加Volatile
关键字之后双重检查锁模式是一种比较好的单列模式实现模式,能够保证在多线程的情况下安全且性能的问题。
- 懒汉式-方式4 (静态内部类方式)
静态内部类单列模式中实列由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的
,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实列化一次,并且严格保证实列化顺序。
package com.jsxs.pattern.singletion.demo5; /** * @Author Jsxs * @Date 2023/4/16 16:02 * @PackageName:com.jsxs.pattern.singletion.demo5 * @ClassName: Singleton * @Description: TODO 静态内部类的方式 * @Version 1.0 */ public class Singleton { // 1.私有构造方法 private Singleton(){} // 2.定义一个静态内部类 private static class SingletonHolder{ //为了防止外部对其进行修改,我们添加一个 final 的关键字 //在内部类中声明并初始化外部类的对象 private final static Singleton instance=new Singleton(); } // 3.提供公共的访问方式 public static Singleton getInstance(){ return SingletonHolder.instance; } }
package com.jsxs.pattern.singletion.demo5; /** * @Author Jsxs * @Date 2023/4/16 16:10 * @PackageName:com.jsxs.pattern.singletion.demo5 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance1 = Singleton.getInstance(); System.out.println(instance1==instance); } }
这里不存在线程安全问题,因为JVM在加载外部类的同时,不会加载静态内部类,只要当静态内部类的属性或者方法被调用的时候才会加载静态内部类。因为是静态变量只能被实列化一次...
说明: 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance(),虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全
,也能保证 Singleton类的唯一性。
小结: 静态内部类单列模式是一种优秀的单列模式
,是开源项目中比较常用的一种单列模式。在没有任何加锁的情况下,保证了多线程的安全,并且没有任何影星性能影响和空间的浪费。
- 枚举方式
枚举类实现单列模式是极力推荐的单列模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用枚举的这个特性来实现单列模式,枚举的写法非常简单,而且枚举类型是所用单列实现中唯一一种不会被破坏的单列实现模式
。
package com.jsxs.pattern.singletion.demo6; /** * @Author Jsxs * @Date 2023/4/16 17:39 * @PackageName:com.jsxs.pattern.singletion.demo6 * @ClassName: Singleton * @Description: TODO 枚举实现方式 * @Version 1.0 */ public enum Singleton { INSTANCE; }
package com.jsxs.pattern.singletion.demo6; /** * @Author Jsxs * @Date 2023/4/16 17:40 * @PackageName:com.jsxs.pattern.singletion.demo6 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) { Singleton instance=Singleton.INSTANCE; Singleton instance1=Singleton.INSTANCE; System.out.println(instance1==instance); } }
枚举方式属于饿汉式方式 -> 如果不考虑内存首选枚举
(5).存在问题-> 破坏单列模式2种
破坏单列模式:
使上面定义的单列类可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射
。
- 序列化反序列化
设置一个静态内部类并且: 实现序列化
package com.jsxs.pattern.singletion.demo7; import java.io.Serializable; /** * @Author Jsxs * @Date 2023/4/16 17:47 * @PackageName:com.jsxs.pattern.singletion.demo7 * @ClassName: Singleton * @Description: TODO * @Version 1.0 */ public class Singleton implements Serializable { // 1.私有构造函数 private Singleton(){} // 2.静态内部类 private static class SingletonHard{ private static final Singleton instance=new Singleton(); } // 3.提供公共的对象 public static Singleton getInstance(){ return SingletonHard.instance; } }
我们首先先写一个数据、然后读取两次
package com.jsxs.pattern.singletion.demo7; import java.io.*; /** * @Author Jsxs * @Date 2023/4/16 17:55 * @PackageName:com.jsxs.pattern.singletion.demo7 * @ClassName: Client * @Description: TODO * @Version 1.0 */ public class Client { public static void main(String[] args) throws Exception { // writeObject2File(); ReadObjectFromFile(); ReadObjectFromFile(); } // 从文件读取数据(对象) public static void ReadObjectFromFile() throws Exception { //1. 创建对象输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\加速器\\a.txt")); //2. 读取对象 Singleton singleton = (Singleton) ois.readObject(); System.out.println(singleton); //3. 释放资源 ois.close(); } // 向文件中写数据 (对象) public static void writeObject2File() throws Exception { //1. 获取Singleton对象 Singleton instance = Singleton.getInstance(); // 2.创建对象输出流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\加速器\\a.txt")); // 3.写对象 oos.writeObject(instance); //4.释放资源 oos.close(); } }
小结: 我们发现对象地址不一致-> 也就是破坏了单列模式。