이 기사에서는 디자인 패턴 중 싱글턴 패턴에 대한 관련 내용을 주로 소개하는 java에 대한 관련 지식을 제공합니다. 싱글턴의 기본 원리는 싱글턴 객체의 클래스가 한 번만 초기화된다는 것입니다. 함께하면 모두에게 도움이 되기를 바랍니다.
추천 학습: "java 비디오 튜토리얼"
단일 요소의 열거형은 싱글톤을 구현하는 가장 좋은 방법인 경우가 많습니다.
싱글톤이란 무엇인가요? 기본 원칙에 따라 싱글톤 객체의 클래스는 한 번만 초기화됩니다. Java에서는 JVM에 클래스의 객체 인스턴스가 하나만 존재한다고 말할 수 있습니다. Android에서는 프로그램이 실행되는 동안 이 클래스의 개체 인스턴스가 하나만 있다고 말할 수 있습니다.
싱글톤 모드의 간단한 구현 단계:
생성자는 비공개이므로 new를 통해 외부에서 객체를 생성할 수 없습니다.
은 이 클래스의 인스턴스를 얻기 위한 외부 정적 메서드를 제공합니다.
클래스 내부에 이 클래스의 객체를 생성하고 2단계의 정적 메서드를 통해 반환합니다.
위 단계에 따라 더 엄격하다고 생각되는 싱글톤 패턴을 작성한 다음 작성한 싱글톤이 다음 조건을 충족할 수 있는지 확인하세요.
- 싱글톤이 요청 시 로드됩니까?
- 싱글톤은 스레드로부터 안전한가요?
동시성의 세 가지 요소인 원자성, 가시성, 질서가 관련됩니다
涉及到并发三要素:原子性、可见性、有序性
- 你的单例暴力反射和序列化安全吗?
一、饿汉式
//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了,相信某些场合枚举所带来的便利远远大于这点所谓的性能影响)
1. Hungry Chinese Style
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()); }</singleton></singleton>🎜🎜장점: 🎜간단한 디자인, 멀티스레드 인스턴스화 문제 해결. 🎜🎜🎜단점: 🎜🎜가상 머신이 SingleTon 클래스를 로드할 때 클래스 정적 변수에는 초기화 단계, 즉 가상 머신이 클래스를 로드할 때 클래스 정적 변수에 값이 할당됩니다. (getInstance 메소드는 호출되지 않을 수 있음) 이때
new SingleTon();
을 호출하면 객체의 인스턴스가 생성됩니다. 이후 인스턴스 객체의 사용 여부에 관계없이 메모리 공간을 차지하게 됩니다. 🎜2. 게으른 남자 스타일
singleton1.hashCode():1296064247 singleton2.hashCode():1637070917🎜🎜장점: 🎜디자인도 비교적 간단합니다. Hungry Man 스타일과 달리 이 싱글톤이 로드되면 static으로 수정된 정적 변수가 null로 초기화됩니다. , 이때는 메모리를 차지하지 않지만, getInstance 메서드가 처음 호출될 때 요청에 따라 인스턴스 개체가 초기화되고 생성됩니다. 🎜🎜🎜단점: 🎜싱글 스레드 환경에서는 문제가 없습니다. 멀티 스레드 환경에서는 스레드 안전 문제가 발생합니다. 두 스레드가 🎜instane == null🎜 문을 동시에 실행하고 둘 다 통과하면 각각 개체를 인스턴스화하므로 더 이상 싱글톤이 아닙니다. 🎜🎜🎜
멀티 스레드 환경에서 게으른 스타일의 다중 인스턴스 문제를 해결하는 방법은 무엇입니까?
🎜🎜🎜🎜🎜정적 내부 클래스🎜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</init>🎜🎜장점:🎜단 하나의 스레드만이 객체를 인스턴스화할 수 있어 스레드 안전 문제를 해결합니다. 🎜🎜🎜단점: 🎜정적 메소드의 경우 동기화 키워드는 getInstance 메소드가 호출될 때마다 스레드가 동기화되므로 인스턴스 객체가 생성된 후에는 동기화가 이루어지지 않습니다. 동기화를 계속해야 합니다. 🎜🎜🎜참고:
여기서 동기화는 작업의 원자성과 메모리 가시성을 보장합니다.
🎜🎜🎜🎜🎜동기화된 코드 블록(이중 확인 잠금 모드 DCL)🎜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()); }</singleton></singleton>🎜🎜장점: 🎜동기화된 코드 블록에 동기화된 코드 블록을 추가하여 인스턴스 객체가 존재하는지 확인합니다. 존재하지 않으면 만들기로 이동하세요. , 인스턴스 개체를 얻기 위해 여러 스레드가 사용되었지만 동시에 하나의 스레드만 동기화 코드 블록에 들어가기 때문에 이번에는 실제로 문제를 완전히 해결할 수 있습니다. 그러면 이때 개체가 생성된 후 다른 경우에도 마찬가지입니다. 스레드는 다시 동기화된 코드 블록에 진입합니다. 인스턴스 객체가 생성되었으므로 직접 반환할 수 있습니다. 그런데 왜 동기화 코드 블록의 이전 단계에서 인스턴스가 다시 비어 있는지 판단해야 합니까? 인스턴스 객체를 생성한 후 인스턴스 객체가 비어 있는지 직접 확인하고, 비어 있지 않으면 직접 반환하면 동기화 코드 블록을 다시 입력할 필요가 없어 성능이 향상되기 때문입니다. 🎜🎜🎜단점: 🎜오브젝트 생성 시 폭력적인 반사를 피할 수 없습니다. 🎜🎜🎜참고:
여기서 휘발성은 메모리 가시성과 명령 재정렬을 방지하는 역할을 합니다.
🎜🎜🎜🎜싱글톤 구현을 위한 열거형
singleton2.hashCode():1296064247 singleton1.hashCode():1637070917🎜싱글톤 구현을 위한 열거형은 직렬화, 리플렉션 등을 통해서도 이를 파괴할 방법이 없기 때문에 가장 권장되는 방법입니다. 싱글턴.
(Android의 열거형 사용이 성능 문제를 일으킬 것이라는 진술과 관련하여 Android 2 이전에는 메모리 부족 시대가 되어야 합니다. 소위 성능 영향으로 인해)
🎜四、如何避免单例模式反射攻击
以最初的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()); }</singleton></singleton>
执行结果:
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</init>
然后我们把上述测试代码修改如下(调换了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()); }</singleton></singleton>
执行结果:
singleton2.hashCode():1296064247 singleton1.hashCode():1637070917
发现此防御未起到作用。
缺点:
- 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
- 如果反射攻击发生在正常调用之后,防御代码是可以生效的;
如何避免序列化攻击?只需要修改反序列化的逻辑就可以了,即重写 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; } }
缺点:
- 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
- 如果反射攻击发生在正常调用之后,防御代码是可以生效的。
(枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性,底层实现比如newInstance方法内部判断枚举抛异常)
推荐学习:《java视频教程》
위 내용은 자바 디자인 패턴의 싱글톤을 함께 분석해보자의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

이 기사에서는 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 및 Gradle을 사용하여 접근 방식과 최적화 전략을 비교합니다.

이 기사에서는 Maven 및 Gradle과 같은 도구를 사용하여 적절한 버전 및 종속성 관리로 사용자 정의 Java 라이브러리 (JAR Files)를 작성하고 사용하는 것에 대해 설명합니다.

이 기사는 카페인 및 구아바 캐시를 사용하여 자바에서 다단계 캐싱을 구현하여 응용 프로그램 성능을 향상시키는 것에 대해 설명합니다. 구성 및 퇴거 정책 관리 Best Pra와 함께 설정, 통합 및 성능 이점을 다룹니다.

이 기사는 캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA를 사용하는 것에 대해 설명합니다. 잠재적 인 함정을 강조하면서 성능을 최적화하기위한 설정, 엔티티 매핑 및 모범 사례를 다룹니다. [159 문자]

Java의 클래스 로딩에는 부트 스트랩, 확장 및 응용 프로그램 클래스 로더가있는 계층 적 시스템을 사용하여 클래스로드, 링크 및 초기화 클래스가 포함됩니다. 학부모 위임 모델은 핵심 클래스가 먼저로드되어 사용자 정의 클래스 LOA에 영향을 미치도록합니다.

이 기사에서는 분산 응용 프로그램을 구축하기위한 Java의 원격 메소드 호출 (RMI)에 대해 설명합니다. 인터페이스 정의, 구현, 레지스트리 설정 및 클라이언트 측 호출을 자세히 설명하여 네트워크 문제 및 보안과 같은 문제를 해결합니다.

이 기사는 네트워크 통신을위한 Java의 소켓 API, 클라이언트 서버 설정, 데이터 처리 및 리소스 관리, 오류 처리 및 보안과 같은 중요한 고려 사항에 대해 자세히 설명합니다. 또한 성능 최적화 기술, i

이 기사에서는 맞춤형 Java 네트워킹 프로토콜을 작성합니다. 프로토콜 정의 (데이터 구조, 프레임, 오류 처리, 버전화), 구현 (소켓 사용), 데이터 직렬화 및 모범 사례 (효율성, 보안, Mainta를 포함합니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

맨티스BT
Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

ZendStudio 13.5.1 맥
강력한 PHP 통합 개발 환경

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

PhpStorm 맥 버전
최신(2018.2.1) 전문 PHP 통합 개발 도구

SecList
SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.
