カテゴリ: 怠け者スタイル、ハングリーマン スタイル
なぜシングルトン モードが必要なのでしょうか?
一部の特殊なケースでは、クラスは一意のオブジェクトを生成するためにのみ使用できる必要があります。たとえば、プリンター ルームには多数のプリンターがありますが、その印刷管理システムには、印刷キューを管理し、各プリンターに印刷タスクを割り当てる印刷タスク制御オブジェクトが 1 つしかありません。シングルトン パターンは、このようなニーズを解決するために作成されました。 実装のアイデア: クライアントがコンストラクターを使用して複数のオブジェクトを作成できないようにするには、コンストラクターをプライベート型として宣言します。ただし、これによりこのクラスが使用できなくなるため、インスタンスを取得できるstatic メソッド (通常はインスタンスを返す getInstance メソッドと呼ばれます) を提供する必要があります。静的メソッドはクラス名に基づいて呼び出されるため、このメソッドは静的である必要があります。静的でない場合は使用できません。
クラス図: Lazy Man スタイル
##クラス図: Hungry Man スタイル
最初に簡単な例を見てみましょう:
シングルトン クラスのテスト: Dog’//懒汉式
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 + "]";
}
}
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));
}
}
遅延スタイルとハングリー スタイルの比較
作成の違い遅延スタイルは、
staticメソッド getInstance() のときにシングルトンを作成することです。初めてオブジェクトに対して呼び出されます。 Hungry Chinese スタイルでは、クラスがロードされるときにシングルトン オブジェクトを作成します。つまり、静的シングルトン オブジェクトを宣言するときにシングルトン クラスをインスタンス化します。
遅延スタイルはスレッド セーフですが、ハングリー スタイルはスレッド セーフです (以下でテストします)。
リソース占有Lazy スタイルは使用時に作成され、Hungry スタイルはクラスのロード時に作成されます。したがって、怠惰な男のスタイルは飢えた男のスタイルほど速くはありませんが、飢えた男のスタイルはより多くのリソースを消費し、常に使用しないと多くのリソースを占有します。
マルチスレッド モードの安全性
マルチスレッド クラス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);
}
}
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's number: "+dogThread.getCount());
}
}
注: マルチスレッドの結果は予測が困難です。ここではスレッドの競合が関係しています。結果は複数回同じになる可能性があります (同じ結果が複数回発生する可能性があります)。同じであるという意味ではありません)。まったく正しいです)、ただし、複数回テストする限り、異なる結果が表示される可能性があります。
説明
ここでは、Set The を使用した、ちょっとしたセット手法を使用します。コレクションの特徴は、毎回生成される犬オブジェクトを Set コレクションに格納し、最後にコレクションの size() メソッドを呼び出すだけです。 2 つの犬オブジェクトが生成されていることがわかります。これは、プログラミング エラーであるエラーが発生したことを意味します。マルチスレッドではエラーが発生しない可能性があるため、生成されるドッグ オブジェクトはスレッドの数よりも小さいことを理解することも重要です。 ハングリースタイルのシングルトンはスレッドセーフであるため、ここではテストしません。興味があればテストしてください。
遅延シングルトン スレッドの安全性に対する解決策: 同期
注: 同期には多くの方法があり、処理にロックを使用することもできます。同期はメソッドであり、特定のメソッドではありません。興味のある方は、同期キーワードについてさらに詳しく調べてください。 また、同期方法は通常時間がかかるため、パフォーマンスも考慮する必要があります。
//静态同步工厂方法 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]; } }テスト クラス
import java.util.HashSet; import java.util.Set; public class Test { public static void main(String[] args) { System.out.println("------------------------"); testMultiInstance(); } //测试多实例模式(单例的扩展,固定数目实例) public static void testMultiInstance() { Set<MultiInstance> instanceSet = new HashSet<>(); MultiInstance instance = null; for (int i = 0; i < 10; i++) { instance = MultiInstance.getInstance(); instanceSet.add(instance); } System.out.println("8个实例中,不同的实例有:"+instanceSet.size()); } }実行結果
注: マルチスレッド環境で使用する場合は、スレッドの安全性も考慮する必要があります。興味があれば、自分で実装することもできます。
#シングルトン モードは必ずしも安全ですか?
必ずしもそうとは限りませんが、シングルトン パターンを打破する方法はたくさんあります。
这里举例看一看(我只能举我知道的哈!其他的感兴趣,可以去探究一下!)
使用反射:这种办法是非常有用的,通过反射即使是私有的属性和方法也可以访问了,因此反射破坏了类的封装性,所以使用反射还是要多多小心。但是反射也有许多其他的用途,这是一项非常有趣的技术(我也只是会一点点)。
使用反射破坏单例模式测试类
这里使用的还是前面的 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)); } }
运行结果
说明
这里可以看出来通过对象序列化(这里也可以说是对象的深拷贝或深克隆),
同样也可以实现类的实例的不唯一性。这同样也算是破坏了类的封装性。对象序列化和反序列化的过程中,对象的唯一性变了。
这里具体的原因很复杂,我最近看了点深拷贝的知识,所以只是知其然不知其之所以然。(所以学习是需要不断进行的!加油诸位。)
这里我贴一下别的经验吧:(感兴趣的可以实现一下!)
为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
这个东西目前超出了我的能力范围了,但也是去查看源码得出来的,就是序列化(serializable)和反序列化(externalizable)接口的详细情况了。但是有一点,它也是通过反射来做的的,所以可以看出**反射(reflect)**是一种非常强大和危险的技术了。
以上がJava シングルトン モードとスレッド セーフティの問題を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。