>  기사  >  Java  >  Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

王林
王林앞으로
2023-05-12 23:07:041402검색

    단일 케이스 모드, 다중 인스턴스 모드 및 스레드 안전성

    단일 케이스 모드

    단일 케이스 모드는 클래스에 하나의 고유 인스턴스만 있고 전역 액세스 포인트를 제공한다는 의미입니다.

    카테고리: 게으른 남자 스타일, 배고픈 남자 스타일

    싱글톤 모드가 필요한 이유는 무엇인가요?

    일부 특별한 경우에는 고유한 개체를 생성하는 데에만 클래스를 사용할 수 있어야 합니다. 예를 들어, 프린터실에는 많은 프린터가 있지만 인쇄 관리 시스템에는 인쇄 대기열을 관리하고 각 프린터에 인쇄 작업을 할당하는 인쇄 작업 제어 개체가 하나만 있습니다. 싱글톤 패턴은 이러한 요구를 해결하기 위해 만들어졌습니다.

    구현 아이디어:

    클라이언트가 생성자를 사용하여 여러 객체를 생성하는 것을 방지하려면 생성자를 비공개 유형으로 선언하세요. 하지만 이렇게 하면 이 클래스를 사용할 수 없게 되므로 인스턴스를 얻을 수 있는 정적 메서드를 제공해야 합니다. 일반적으로 인스턴스를 반환하는 getInstance 메서드라고 합니다. 정적 메서드는 클래스 이름을 기반으로 호출되므로 이 메서드는 정적이어야 합니다. 그렇지 않으면 사용할 수 없습니다.

    클래스 다이어그램: Lazy Chinese style

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    클래스 다이어그램: Hungry Chinese style

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    먼저 간단한 예를 살펴보겠습니다.

    싱글톤 클래스 테스트: Dog’

    rrre 에에

    테스트 싱글턴 클래스: Cat

    //懒汉式
    public class Dog {
    	private static Dog dog;
    	private String name;
    	private int age;
    	
    	//私有的构造器
    	private Dog() {}
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	//静态工厂方法
    	public static Dog getInstance() {
    		if (dog == null) {
    			dog = new Dog();
    		}
    		return dog;
    	}
    
    	@Override
    	public String toString() {
    		return "Dog [name=" + name + ", age=" + age + "]";
    	}
    }

    테스트 클래스

    //饿汉式
    public class Cat {
    	private static Cat cat = new Cat();
    	private String name;
    	private int age;
    	
    	//私有构造器
    	private Cat() {}
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	public int getAge() {
    		return age;
    	}
    	
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	//静态工厂方法
    	public static Cat getInstance() {
    		return cat;
    	}
    
    	@Override
    	public String toString() {
    		return "Cat [name=" + name + ", age=" + age + "]";
    	}
    }

    실행 결과

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    게으른 스타일과 배고픈 스타일의 비교

    차이 만들기

    처음으로 게으른 스타일이 호출됩니다 싱글톤 객체는 정적 메서드 getInstance()가 사용될 때 생성됩니다.
    Hungry 스타일은 클래스가 로드될 때 싱글톤 객체를 생성하는 것입니다. 즉, 정적 싱글톤 객체를 선언할 때 싱글톤 클래스를 인스턴스화합니다.

    스레드 안전성

    게으른 스타일은 스레드에 안전하지 않은 반면, Hungry 스타일은 스레드에 안전합니다(아래에서 테스트됩니다).

    자원 점유

    Lazy 스타일은 사용하기 전까지 생성되지 않는 반면, Hungry 스타일은 클래스가 로드될 때 생성됩니다. 그러므로 게으른 사람의 스타일은 배고픈 사람의 스타일만큼 빠르지는 않지만, 배고픈 사람의 스타일은 항상 사용하지 않으면 많은 리소스를 차지하게 됩니다.

    멀티 스레드 모드의 안전성

    멀티 스레드 클래스

    import java.util.HashSet;
    import java.util.Set;
    
    public class Client {
    
    	public static void main(String[] args) {
    		//单线程模式测试
    		Dog dog1 = Dog.getInstance();
    		Dog dog2 = Dog.getInstance();
    		System.out.println("dog1 == dog2: "+(dog1 == dog2));
    		
    		Cat cat1 = Cat.getInstance();
    		Cat cat2 = Cat.getInstance();
    		System.out.println("cat1 == cat2: "+(cat1 == cat2));
    	}
    }

    멀티 스레드 테스트 클래스

    import java.util.HashSet;
    import java.util.Set;
    
    public class DogThread extends Thread{
    	private Dog dog;
    	private Set<Dog> set;
    	
    	public DogThread() {
    		set = new HashSet<>();
    	}
    	
    	//这个方法是为了测试添加的。
    	public int getCount() {
    		return set.size();
    	}
    	
    	@Override
    	public void run() {
    		dog = Dog.getInstance();
    		set.add(dog);
    	}
    }

    실행 결과
    참고: 멀티 스레드의 결과는 예측하기 어렵습니다. 여기에 관련됩니다. 대회에서는 결과가 여러 번 동일할 수 있지만(여러 번 동일하다고 해서 절대적으로 올바른 것은 아닙니다), 여러 번 테스트하는 한 다른 결과를 볼 수 있습니다.

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    Explanation

    여기서는 Set 컬렉션의 특성을 활용하여 약간의 컬렉션 기술을 사용하여 매번 생성되는 개 개체를 Set 컬렉션에 저장하고 마지막으로 크기( ) 컬렉션 방법 그게 다입니다. 두 개의 dog 객체가 생성된 것을 볼 수 있는데, 이는 프로그래밍 오류인 오류가 발생했음을 의미합니다. 멀티스레딩에서는 오류가 발생하지 않을 수 있으므로 생성되는 Dog 개체가 스레드 수보다 적다는 점을 이해하는 것도 중요합니다.
    Hungry 스타일 싱글톤은 스레드로부터 안전하므로 여기서는 테스트하지 않습니다. 관심이 있는 경우 테스트해 볼 수 있습니다.

    게으른 싱글톤 스레드 안전성에 대한 솔루션: 동기화
    참고: 동기화 방법에는 잠금을 사용할 수도 있습니다. 동기화는 특별히 동기화라는 키워드가 아닌 방법입니다. .
    그리고 동기화 방법은 일반적으로 속도가 느리고 성능 측면에서 평가를 받아야 합니다.

    import java.util.HashSet;
    import java.util.Set;
    
    public class Client {
    
    	public static void main(String[] args) {
    		//单线程模式测试
    		Dog dog1 = Dog.getInstance();
    		Dog dog2 = Dog.getInstance();
    		System.out.println("dog1 == dog2: "+(dog1 == dog2));
    		
    		Cat cat1 = Cat.getInstance();
    		Cat cat2 = Cat.getInstance();
    		System.out.println("cat1 == cat2: "+(cat1 == cat2));
    		
    		//多线程模式测试
    		DogThread dogThread = new DogThread();
    		Thread thread = null;
    		for (int i = 0; i < 10; i++) {
    			thread = new Thread(dogThread);
    			thread.start();	
    		}
    		
    		try {
    			Thread.sleep(2000); //主线程等待子线程完成!
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("dog&#39;s number: "+dogThread.getCount());
    	}
    }

    멀티 인스턴스 모드

    여기에는 멀티 인스턴스 모드가 있습니다. 즉, 개체 수가 고정되어 있습니다. 싱글톤 패턴의 승격을 볼 수 있습니다. 물론 이를 구현하는 방법은 다양합니다. 다음은 제 방법입니다.

    다중 인스턴스 모드 클래스

    	//静态同步工厂方法
    	public synchronized static Dog getInstance() {
    		if (dog == null) {
    			dog = new Dog();
    		}
    		return dog;
    	}

    테스트 클래스

    //固定数目实例模式
    public class MultiInstance {
    	//实例数量,这里为四个
    	private final static int INSTANCE_COUNT = 4;
    	private static int COUNT = 0;
    	private static MultiInstance[] instance = new MultiInstance[4];
    	
    	private MultiInstance() {};
    	
    	public static MultiInstance getInstance() {
    		//注意数组的下标只能为 COUNT - 1
    		if (MultiInstance.COUNT <= MultiInstance.INSTANCE_COUNT - 1) {
    			instance[MultiInstance.COUNT] = new MultiInstance();
    			MultiInstance.COUNT++;
    		}
    		//返回实例前,执行了 COUNT++ 操作,所以 应该返回上一个实例
    		return MultiInstance.instance[MultiInstance.COUNT-1];  
    	}
    }

    실행 결과
    참고: 멀티 스레드 환경에서 사용하는 경우 스레드 안전성도 고려해야 합니다. 관심이 있으시면 직접 구현해 보시기 바랍니다.

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    싱글턴 패턴은 반드시 안전한가요?

    반드시 그런 것은 아니지만, 싱글톤 패턴을 깨는 방법은 여러 가지가 있습니다!

    这里举例看一看(我只能举我知道的哈!其他的感兴趣,可以去探究一下!)
    使用反射:这种办法是非常有用的,通过反射即使是私有的属性和方法也可以访问了,因此反射破坏了类的封装性,所以使用反射还是要多多小心。但是反射也有许多其他的用途,这是一项非常有趣的技术(我也只是会一点点)。

    使用反射破坏单例模式测试类

    这里使用的还是前面的 Dog 实体类。注意我这里的**包名:**com。
    所有的类都是在 com包 下面的。

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public class Client {
    	public static void main(String[] args) throws 
    	ClassNotFoundException, 
    	NoSuchMethodException, 
    	SecurityException, 
    	InstantiationException, 
    	IllegalAccessException, 
    	IllegalArgumentException, 
    	InvocationTargetException {
    	
    		Class<?> clazz = Class.forName("com.Dog");
    		Constructor<?> con = clazz.getDeclaredConstructor();
    		//设置可访问权限
    		con.setAccessible(true);
    		Dog dog1 = (Dog) con.newInstance();
    		Dog dog2 = (Dog) con.newInstance();
    		System.out.println(dog1 == dog2);
    	}
    }

    说明:反射的功能是很强大的,从这里既可以看出来,正是有了反射,才使得Java 语言具有了更多的特色,这也是Java的强大之处。

    使用对象序列化破坏单例模式

    测试实体类:Dog(增加一个对象序列化接口实现)

    import java.io.Serializable;
    //懒汉式
    public class Dog implements Serializable{
    	private static final long serialVersionUID = 1L;
    	
    	private static Dog dog;
    	private String name;
    	private int age;
    	
    	//私有的构造器
    	private Dog() {}
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	//静态工厂方法
    	public synchronized static Dog getInstance() {
    		if (dog == null) {
    			dog = new Dog();
    		}
    		return dog;
    	}
    
    	@Override
    	public String toString() {
    		return "Dog [name=" + name + ", age=" + age + "]";
    	}
    }

    对象序列化测试类

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class Client {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    		Dog dog1 = Dog.getInstance();
    		dog1.setName("小黑");
    		dog1.setAge(2);
    		System.out.println(dog1.toString());
    		
    		ByteArrayOutputStream bos = new ByteArrayOutputStream();
    		ObjectOutputStream oos = new ObjectOutputStream(bos);
    		oos.writeObject(dog1);
    		
    		
    		ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    		ObjectInputStream ois = new ObjectInputStream(bis);
    		Dog dog2 = (Dog) ois.readObject();
    		System.out.println(dog2.toString());
    		System.out.println("dog1 == dog2: "+(dog1 == dog2));
    		
    	}
    }

    运行结果

    Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법

    说明
    这里可以看出来通过对象序列化(这里也可以说是对象的深拷贝或深克隆),
    同样也可以实现类的实例的不唯一性。这同样也算是破坏了类的封装性。对象序列化和反序列化的过程中,对象的唯一性变了。

    这里具体的原因很复杂,我最近看了点深拷贝的知识,所以只是知其然不知其之所以然。(所以学习是需要不断进行的!加油诸位。)
    这里我贴一下别的经验吧:(感兴趣的可以实现一下!)

    为什么序列化可以破坏单例了?
    答:序列化会通过反射调用无参数的构造方法创建一个新的对象。

    这个东西目前超出了我的能力范围了,但也是去查看源码得出来的,就是序列化(serializable)和反序列化(externalizable)接口的详细情况了。但是有一点,它也是通过反射来做的的,所以可以看出**反射(reflect)**是一种非常强大和危险的技术了。

    위 내용은 Java 싱글톤 모드 및 스레드 안전 문제를 해결하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제