A.は前に言いました
システム開発の設計では、常にそのようなツール、データベースや頻繁にアクセスされるファイルなどのオブジェクト(②オブジェクトがリソースを大量に消費する必要が作成しますが、頻繁に使用されるオブジェクトのオブジェクト、破壊されたオブジェクトを作成するために頻繁に必要①、ほんの数例があるでしょう、データソース、セッション工場など);③クラスは、アプリケーションベースのアプリケーションとして、1つのオブジェクトのみを有し、それらは、シングルトンを使用して検討すべきです。個人ブログのアドレスwww.mycookies.cn
II。動機Singletonパターン
- ソフトウェアシステムでは、多くの場合、いくつかの特別なクラスを持って、彼らはシステムにあることを確認する必要がありますインスタンスが1つしかない彼らの論理的な正しさを保証するため、として、効率よく
- クラスのインスタンスを1つだけ確実にするためにメカニズムを提供し、従来の構成をバイパスするには?それはむしろ、ユーザーの責任よりも、デザインクラスの責任でなければなりません
III。スキーマ定義
确保类只有一个实例,并提供全局访问点。
空腹中国風
クラス初期化の完了後にかかわらず、事前にインスタンス化されるべきかどうかの、完全なオブジェクトが作成されています。
/**
* 饿汉式[工厂方法]
*
* @author Jann Lee
* @date 2019-07-21 14:28
*/
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return instance;
}
}
/**
* 饿汉式[公有域]
*/
public class Singleton2 {
public static final Singleton2 instance = new Singleton2();
private Singleton2() {
}
}
/**
* 饿汉式[静态代码块,工厂方法]
*/
public class Singleton3 {
private static Singleton3 instance;
static {
instance = new Singleton3();
}
private Singleton3() {
}
public static Singleton3 getInstance() {
return instance;
}
}
これらの3つは、最も一般的な実装である、「空腹の男のスタイル。」を達成するために、コードの文言で唯一の違いです
利点は:マルチスレッドの問題を避けるため、完全な具体化で時間内に囲まれている
欠点:(例はに使用されることはありませんかもしれません)メモリの無駄が発生することがあり
レイジースタイル
その名の通り、私は怠け者だったので使用された場合、そのインスタンス化されます。
アイデアの実現:民営化コンストラクタ - >メンバ変数を宣言 - > [あなたはメンバ変数へのパブリックアクセスを提供する場合スペースが復帰のために作成されている場合、ダイレクトリターン空ではありません]
/**
* 1.懒汉式[非线程安全]
*
* @author Jann Lee
* @date 2019-07-21 14:31
**/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1() {
}
public static Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
上記の実装は、オブジェクトのより多くのマルチスレッド環境下で数回作成されている可能性があるため、最も簡単に考えるが、それはまた間違った実装とみなされるべきです。
スレッドの安全性の問題を解決するために、メソッドが同期されます
/**
* 2.懒汉式[同步方法]
**/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {
}
/**
* 同步方法,保证线程安全
*/
public static synchronized Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
確かに、同時事情に起因する問題を解決するには、この実現。しかし、すべての取得オブジェクトを同期させる必要がありますが、オブジェクトは一度しか作成することができ、同時のデータが同期される必要はなく読み込むので、同期して、この方法は必要ありませんので、同時実行に持参してくださいです性能の損失。
この時点で、我々は最初の同期ブロックの実装を使用するオブジェクトを作成するときにのみ使用を同期、同期の範囲を狭めると思います。
/**
* 4.懒汉式[同步代码块]
**/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
/**
* 同步代码块,并不能保证线程安全
*/
public static Singleton3 getInstance() {
if (instance == null) {
synchronized (Singleton3.class) {
instance = new Singleton3();
}
}
return instance;
}
}
这是一种错误的实现方式,虽然减少了加锁范围,但是又回到了并发环境下的重复创建对象的问题,具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争,线程1获取到了锁,线程2挂起,执行完毕后释放锁,此时线程二获取到锁后回接着往下执行,再次创建对象。
解决上述问题,我们只需要在同步代码块中加入校验。
/**
* 懒汉式[双重检查锁]
**/
public class Singleton4 {
private static volatile Singleton4 instance;
private Singleton4() {
}
public static Singleton4 getInstance() {
if (instance == null) {
synchronized (Singleton4.class) {
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
注意:此处除了再次校验是否为空之外,还给成员变量之前加上了一个volatile关键字,这个非常重要。因为在代码执行过程中会发生指令重排序。这里你只需要知道加上volatile之后能保证指令不会被重排序,程序能正确执行,而不加则可能不出错。
在编译器和处理器为了提高程序的运行性能,会对指令进行重新排序。即代码会被编译成操作指令,而指令执行顺序可能会发生变化。
java中创建对象分为 三个步骤【可以简单理解为三条指令】
- 分配内存,内存空间初始化
- 对象初始化,类的元数据信息,hashCode等信息
- 将内存地址返回
如果2,3顺序发生了变化,另一个线程获得锁时恰好还没有完成对象初始化,即instance指向null,就会重复创建对象。
静态内部类
在静态内部类中持有一个对象。
/**
* 使用静态内部类实现单例模式
*
* @author Jann Lee
* @date 2019-07-21 14:47
**/
public class Singleton {
private Singleton (){}
public static Singleton getInstance() {
return ClassHolder.singleton;
}
/**
* Singleton装载完成后,不会创建对象
* 调用getInstance时候,静态内部类ClassHolder才进行装载
*/
private static class ClassHolder {
private static final Singleton singleton = new Singleton();
}
}
静态内部类的实现则是根据java语言特性实现的,即让静态内部类持有一个对象;根据类加载机机制的特点,每个类只会加载一次,并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次,无论是否在多线程环境中。
只包含单个元素的枚举类型
在java中枚举也是类的一种,实际上枚举的每一个元素都是一个枚举类的对象,可以理解为对类的一种封装,默认私有构造方法,且不能使用public修饰。
/**
* 枚举实现单例模式
*
* 为了便于理解给枚举类添加了两个属性
* @author Jann Lee
* @date 2019-07-21 14:55
**/
public enum Singleton {
INSTANCE;
private String name;
private int age;
Singleton04() {
}
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;
}
}
四.要点总结
- SIngleton模式中的实例构造器可以设置为protected以允许字类派生
- Singleton模式一般不要支持拷贝构造函数和clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
- 如何实现多线程环境下安全的Singleton,注意对双重检查锁的正确实现。
五.思考
- java中创建对象的方式有很多种,其中当然包括反射,反序列化,那么上述各种设计模式还能保证对象只会被创建一次吗?(这个问题会在下一篇 中进行分析)
- volatile关键字是一个非常重要的关键字,它有那些功能?