Dieser Artikel vermittelt Ihnen relevantes Wissen über Java, das hauptsächlich den relevanten Inhalt des Singleton-Musters im Entwurfsmuster vorstellt. Das Grundprinzip von Singleton besteht darin, dass die Klasse des Singleton-Objekts nur einmal initialisiert wird Wenn wir gemeinsam daran arbeiten, hoffe ich, dass es für alle hilfreich sein wird.
Empfohlene Studie: „Java-Video-Tutorial“
Der Aufzählungstyp eines einzelnen Elements ist oft der beste Weg, Singleton zu implementieren.
Was ist ein Singleton? Als Grundprinzip gilt: Die Klasse eines Singleton-Objekts wird nur einmal initialisiert. In Java können wir sagen, dass in der JVM nur eine Objektinstanz der Klasse existiert. In Android können wir sagen, dass es während der Ausführung des Programms nur eine Objektinstanz dieser Klasse gibt.
Einfache Implementierungsschritte des Singleton-Modus:
Der Konstruktor ist privat und stellt sicher, dass Objekte nicht von außen durch neue erstellt werden können.
stellt externe statische Methoden bereit, um Instanzen dieser Klasse abzurufen.
Erstellen Sie ein Objekt dieser Klasse innerhalb der Klasse und geben Sie es über die statische Methode in Schritt 2 zurück.
Befolgen Sie die obigen Schritte, um ein Singleton-Muster aufzuschreiben, das Ihrer Meinung nach strenger ist, und prüfen Sie dann, ob der von Ihnen geschriebene Singleton die folgenden Bedingungen erfüllen kann:
Beinhaltet drei Elemente der Parallelität: Atomizität, Sichtbarkeit, Ordnung
涉及到并发三要素:原子性、可见性、有序性
//JAVA实现public class SingleTon { //第三步创建唯一实例 private static SingleTon instance = new SingleTon(); //第一步构造方法私有 private SingleTon() { } //第二步暴露静态方法返回唯一实例 public static SingleTon getInstance() { return instance; } }//Kotlin实现object SingleTon
优点:设计简单 ,解决了多线程实例化的问题。
缺点:在虚拟机加载SingleTon类的时候,将会在初始化阶段为类静态变量赋值,也就是在虚拟机加载该类的时候(此时可能并没有调用 getInstance 方法)就已经调用了 new SingleTon();
创建了该对象的实例,之后不管这个实例对象用不用,都会占据内存空间。
//JAVA实现public class SingleTon { //创建唯一实例 private static SingleTon instance = null; private SingleTon() { } public static SingleTon getInstance() { //延迟初始化 在第一次调用 getInstance 的时候创建对象 if (instance == null) { instance = new SingleTon(); } return instance; } }//Kotlin实现class SingleTon private constructor() { companion object { private var instance: SingleTon? = null get() { if (field == null) { field = SingleTon() } return field } fun get(): SingleTon{ return instance!! } } }
优点:设计也是比较简单的,和饿汉式不同,当这个Singleton被加载的时候,被static修饰的静态变量将会被初始化为null,这个时候并不会占用内存,而是当第一次调用getInstance方法的时候才会被初始化实例对象,按需创建。
缺点:在单线程环境下是没有问题的,在多线程环境下,会产生线程安全问题。在有两个线程同时 运行到了 instane == null这个语句,并且都通过了,那他们就会都各自实例化一个对象,这样就又不是单例了。
如何解决懒汉式在多线程环境下的多实例问题?
静态内部类
//JAVA实现public class SingleTon { private static class InnerSingleton{ private static SingleTon singleTon = new SingleTon(); } public SingleTon getInstance(){ return InnerSingleton.singleTon; } private SingleTon() { } }//kotlin实现class SingleTon private constructor() { companion object { val instance = InnerSingleton.instance } private object InnerSingleton { val instance = SingleTon() } }
直接同步方法
//JAVA实现public class SingleTon { //创建唯一实例 private static SingleTon instance = null; private SingleTon() { } public static synchronized SingleTon getInstance() { if (instance == null) { instance = new SingleTon(); } return instance; } }//Kotlin实现class SingleTon private constructor() { companion object { private var instance: SingleTon? = null get() { if (field == null) { field = SingleTon() } return field } @Synchronized fun get(): SingleTon{ return instance!! } } }
优点:加锁只有一个线程能实例该对象,解决了线程安全问题。
缺点:对于静态方法而言,synchronized关键字会锁住整个 Class,每次调用getInstance方法都会线程同步,效率十分低下,而且当创建好实例对象之后,也就不必继续进行同步了。
备注:此处的synchronized保证了操作的原子性和内存可见性。
同步代码块(双重检锁方式DCL)
//JAVA实现 public class SingleTon { //创建唯一实例 private static volatile SingleTon instance = null; private SingleTon() { } public static SingleTon getInstance() { if (instance == null) { synchronized (SingleTon.class) { if (instance == null) { instance = new SingleTon(); } } } return instance; } }//kotlin实现class SingleTon private constructor() { companion object { val instance: SingleTon by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SingleTon() } } } 或者class SingleTon private constructor() { companion object { @Volatile private var instance: SingleTon? = null fun getInstance() = instance ?: synchronized(this) { instance ?: SingleTon().also { instance = it } } } }
优点:添加了一个同步代码块,在同步代码块中去判断实例对象是否存在,如果不存在则去创建,这个时候其实就完全可以解决问题了,因为虽然是多个线程去获取实例对象,但是在同一个时间也只会有一个线程会进入到同步代码块,那么这个时候创建好对象之后,其他线程即便再次进入同步代码块,由于已经创建好了实例对象,便直接返回即可。但是为什么还要在同步代码块的上一步再次去判断instance为空呢?这个是由于当我们创建好实例对象之后,直接去判断此实例对象是否为空,如果不为空,则直接返回就好了,就避免再次进去同步代码块了,提高了性能。
缺点:无法避免暴力反射创建对象。
备注:此处的volatile发挥了内存可见性及防止指令重排序作用。
public enum SingletonEnum { INSTANCE; public static void main(String[] args) { System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE); } }
枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性。(关于Android使用枚举会产生性能问题的说法,这应该是Android 2.x系统之前内存紧张的时代了,现在已经Android 13了,相信某些场合枚举所带来的便利远远大于这点所谓的性能影响)
public static void main(String[] args) { SingleTon singleton1 = SingleTon.getInstance(); SingleTon singleton2 = null; try { Class<SingleTon> clazz = SingleTon.class; Constructor<SingleTon> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("singleton1.hashCode():" + singleton1.hashCode()); System.out.println("singleton2.hashCode():" + singleton2.hashCode()); }
new SingleTon();
eine Instanz des Objekts erstellt, unabhängig davon, ob das Instanzobjekt verwendet wird oder nicht. 🎜singleton1.hashCode():1296064247 singleton2.hashCode():1637070917
Wie löst man das Lazy-Style-Multiinstanzproblem in einer Multithread-Umgebung?
🎜🎜🎜🎜🎜Statische innere Klasse🎜public class SingleTon { //创建唯一实例 private static volatile SingleTon instance = null; private SingleTon() { if (instance != null) { throw new RuntimeException("单例构造器禁止反射调用"); } } public static SingleTon getInstance() { if (instance == null) { synchronized (SingleTon.class) { if (instance == null) { instance = new SingleTon(); } } } return instance; } }
java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45) at com.imock.demo.TestUtil.main(TestUtil.java:33) Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用 at com.imock.demo.SingleTon.<init>(SingleTon.java:16) ... 6 more Exception in thread "main" java.lang.NullPointerException at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49) at com.imock.demo.TestUtil.main(TestUtil.java:33) Process finished with exit code 1
Die Synchronisierung hier stellt die Atomizität und Speichersichtbarkeit des Vorgangs sicher.
🎜🎜🎜🎜🎜Synchronisierter Codeblock (Sperrmodus-DCL mit doppelter Überprüfung)🎜public static void main(String[] args) { SingleTon singleton2 = null; try { Class<SingleTon> clazz = SingleTon.class; Constructor<SingleTon> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("singleton2.hashCode():" + singleton2.hashCode()); SingleTon singleton1 = SingleTon.getInstance(); //调换了位置,在反射之后执行 System.out.println("singleton1.hashCode():" + singleton1.hashCode()); }
Das flüchtige Element spielt hier eine Rolle bei der Speichersichtbarkeit und verhindert die Neuordnung von Anweisungen.
🎜🎜🎜🎜singleton2.hashCode():1296064247 singleton1.hashCode():1637070917
(Bezüglich der Aussage, dass die Verwendung der Aufzählung durch Android zu Leistungsproblemen führen wird, sollte dies die Ära des knappen Speichers vor Android 2 sein. Aufgrund dieser sogenannten Leistungsbeeinträchtigung)
🎜以最初的DCL为测试案例,看看如何进行反射攻击及又如何在一定程度上避免反射攻击。
反射攻击代码如下:
public static void main(String[] args) { SingleTon singleton1 = SingleTon.getInstance(); SingleTon singleton2 = null; try { Class<SingleTon> clazz = SingleTon.class; Constructor<SingleTon> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("singleton1.hashCode():" + singleton1.hashCode()); System.out.println("singleton2.hashCode():" + singleton2.hashCode()); }
执行结果:
singleton1.hashCode():1296064247 singleton2.hashCode():1637070917
通过执行结果发现通过反射破坏了单例。 如何保证反射安全呢?只能以暴制暴,当已经存在实例的时候再去调用构造函数直接抛出异常,对构造函数做如下修改:
public class SingleTon { //创建唯一实例 private static volatile SingleTon instance = null; private SingleTon() { if (instance != null) { throw new RuntimeException("单例构造器禁止反射调用"); } } public static SingleTon getInstance() { if (instance == null) { synchronized (SingleTon.class) { if (instance == null) { instance = new SingleTon(); } } } return instance; } }
此时可防御反射攻击,抛出异常如下:
java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45) at com.imock.demo.TestUtil.main(TestUtil.java:33) Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用 at com.imock.demo.SingleTon.<init>(SingleTon.java:16) ... 6 more Exception in thread "main" java.lang.NullPointerException at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49) at com.imock.demo.TestUtil.main(TestUtil.java:33) Process finished with exit code 1
然后我们把上述测试代码修改如下(调换了singleton1的初始化顺序)
:
public static void main(String[] args) { SingleTon singleton2 = null; try { Class<SingleTon> clazz = SingleTon.class; Constructor<SingleTon> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("singleton2.hashCode():" + singleton2.hashCode()); SingleTon singleton1 = SingleTon.getInstance(); //调换了位置,在反射之后执行 System.out.println("singleton1.hashCode():" + singleton1.hashCode()); }
执行结果:
singleton2.hashCode():1296064247 singleton1.hashCode():1637070917
发现此防御未起到作用。
缺点:
如何避免序列化攻击?只需要修改反序列化的逻辑就可以了,即重写 readResolve()
方法,使其返回统一实例。
protected Object readResolve() { return getInstance(); }
脆弱不堪的单例模式经过重重考验,进化成了完全体,延迟加载,线程安全,反射及序列化安全。简易代码如下:
饿汉模式
public class SingleTon { private static SingleTon instance = new SingleTon(); private SingleTon() { if (instance != null) { throw new RuntimeException("单例构造器禁止反射调用"); } } public static SingleTon getInstance() { return instance; } }
静态内部类
public class SingleTon { private static class InnerStaticClass{ private static SingleTon singleTon = new SingleTon(); } public SingleTon getInstance(){ return InnerStaticClass.singleTon; } private SingleTon() { if (InnerStaticClass.singleTon != null) { throw new RuntimeException("单例构造器禁止反射调用"); } } }
懒汉模式
public class SingleTon { //创建唯一实例 private static SingleTon instance = null; private SingleTon() { if (instance != null) { throw new RuntimeException("单例构造器禁止反射调用"); } } public static SingleTon getInstance() { //延迟初始化 在第一次调用 getInstance 的时候创建对象 if (instance == null) { instance = new SingleTon(); } return instance; } }
缺点:
(枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性,底层实现比如newInstance方法内部判断枚举抛异常)
推荐学习:《java视频教程》
Das obige ist der detaillierte Inhalt vonLassen Sie uns gemeinsam den Singleton des Java-Entwurfsmusters analysieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!