Spring:単例モード知識記録
11467 ワード
目次
従来のクラスコードの作成
単一クラスの作成
(1)餓漢モード(空間で時間を変えると、資源消費の問題がある可能性がある)
(2)怠け者モード(スレッドが安全でないという問題がある可能性がある)
(3)メソッドにsynchronizedロックを追加する怠け者モード(システムボトルネックをもたらす)
(3)二重チェックロックを追加した怠け者モード(JVM命令再編成でプログラムエラーが発生する可能性がある)
(4)二重検査ロックとvolatileキーワードを追加する怠け者モード
(5)静的内部クラスの一例パターン(推奨の書き方)
Springシングルモードソース
Singleton Pattern:クラスが1つのインスタンスしかないことを確認し、独自にインスタンス化し、システム全体にこのインスタンスを提供します.このクラスは単一のインスタンスクラスと呼ばれ、グローバルアクセスの方法を提供します.単一モードは、オブジェクト作成モードです.
従来のクラスコードの作成
new Singleton()のたびにSingletonインスタンスが作成され、単一のインスタンスクラスに1つのインスタンスしかないという要件に合致しません.
単一クラスの作成
(1)餓漢モード(空間で時間を変えると、資源消費の問題がある可能性がある)
Step 1.コンストラクション関数のプライベート化.
Step 2.自ら実例を外部に提供する.
Step 3.外部からこのインスタンスを取得できる方法を提供する.
(2)怠け者モード(スレッドが安全でないという問題がある可能性がある)
単一のインスタンスオブジェクトを作成すると、大量のリソースが消費されるため、怠け者モードでオブジェクトの作成を遅らせることができます.しかし、マルチスレッドが同時に使用される場合、怠け者モードを使用するとgetInstance()メソッドが同時に呼び出され、システムが複数の単一クラスインスタンスを同時に作成することになります.
(3)メソッドにsynchronizedロックを追加する怠け者モード(システムボトルネックをもたらす)
synchronizedロックを追加するとスレッドのセキュリティが保証されますが、getInstance()メソッドにアクセスするたびにロックとロック解除の操作が行われます.synchronizedロックはメソッドに追加され、作用範囲が大きすぎます.一方、単一のクラスはグローバルで唯一であり、ロックの操作はシステムのボトルネックになります.
(3)二重チェックロックを追加した怠け者モード(JVM命令再編成でプログラムエラーが発生する可能性がある)
上記のコードでは、singleton=new Singleton()命令は、実際に以下のJVM命令として抽象化できます.
ここで,操作2は操作1に依存するが,操作3は操作2に依存しないため,JVMはそれらに対して命令最適化再ソートを行う.
命令再配置:JVMが命令を最適化し、プログラムの実行効率を高めるために、単一スレッドプログラムの実行結果に影響を与えない前提で、できるだけ並列度を高めることを指す.
JVM命令の並べ替えは次のようになります.
スレッドAがこの付与文を実行している場合、割り当てオブジェクトを初期化する前にsingleton参照に付与され、スレッドBがこのメソッドに入ってsingleton参照がnullではないと判断し、それを返して使用すると、プログラムエラーが発生します.
(4)二重検査ロックとvolatileキーワードを追加する怠け者モード
volatileキーワードを使用してsingletonフィールドを修飾すると、命令の並べ替え最適化を禁止し、JVMが関連コードの命令並べ替えを阻止し、既定の順序で命令を実行することを保証します.
(5)静的内部クラスの一例パターン(推奨の書き方)
Springシングルモードソース
Springの依存注入(lazy-init方式を含む)はAbstractBeanFactoryのgetBean()メソッドで発生する.getBean()メソッド内でdoGetBean()メソッドを呼び出し、doGetBean()メソッド内で親クラスFactoryBeanRegistrySupportの親クラスDefaultSingletonBeanRegistryのgetSingleton()メソッドを呼び出してBeanの作成を行います.非lazy-init方式では、コンテナの初期化時に呼び出され、lazy-init方式では、ユーザがコンテナにbeanを最初に要求したときに呼び出されます.
getBean()、doGetBean()およびgetSingleton()メソッドのソースコードは、次のようになります.
getSingleton()法から,Springが依存注入を行う場合,二重チェックロックを用いた一例モードが見られる.
まず、beanインスタンスをキャッシュsingletonObjects(実際にはConcurrentHashMap)から取得します.
SpringソースD e f a ultSingletonBeanRegistryクラスではsingletonObjectsを次のように定義しています.
private final Map singletonObjects = new ConcurrentHashMap(256);
beanインスタンスがnullの場合は、キャッシュsingletonObjectsにロックをかけ、キャッシュearlySingletonObjects(実際にはHashMap)からbeanインスタンスを取得し、nullの場合はbeanを作成します.
SpringソースD e f a ultSingletonBeanRegistryクラスでは、earlySingletonObjectsを次のように定義しています.
private final Map earlySingletonObjects = new HashMap(16);
前述したシングル・インスタンス・モードがプライベート・コンストラクション・メソッドを使用してbeanを作成するのとは異なり、ここではsingletonFactory.getObject()によって特定のbeanNameに対応するObjectFactoryを返してbeanを作成する.実際には、AbstractAutowireCapableBeanFactoryのdoCreateBean()メソッドを呼び出し、BeanWrapperパッケージで作成されたbeanインスタンスを返します.
doCreateBean()メソッドの一部のコードは次のとおりです.
従来のクラスコードの作成
単一クラスの作成
(1)餓漢モード(空間で時間を変えると、資源消費の問題がある可能性がある)
(2)怠け者モード(スレッドが安全でないという問題がある可能性がある)
(3)メソッドにsynchronizedロックを追加する怠け者モード(システムボトルネックをもたらす)
(3)二重チェックロックを追加した怠け者モード(JVM命令再編成でプログラムエラーが発生する可能性がある)
(4)二重検査ロックとvolatileキーワードを追加する怠け者モード
(5)静的内部クラスの一例パターン(推奨の書き方)
Springシングルモードソース
Singleton Pattern:クラスが1つのインスタンスしかないことを確認し、独自にインスタンス化し、システム全体にこのインスタンスを提供します.このクラスは単一のインスタンスクラスと呼ばれ、グローバルアクセスの方法を提供します.単一モードは、オブジェクト作成モードです.
従来のクラスコードの作成
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
Singleton singleton1 = new Singleton();
Singleton singleton2 = new Singleton();
}
}
/**
* @author:py
* @date:
* @description:
* @version:
*/
class Singleton {
}
new Singleton()のたびにSingletonインスタンスが作成され、単一のインスタンスクラスに1つのインスタンスしかないという要件に合致しません.
単一クラスの作成
(1)餓漢モード(空間で時間を変えると、資源消費の問題がある可能性がある)
Step 1.コンストラクション関数のプライベート化.
Step 2.自ら実例を外部に提供する.
Step 3.外部からこのインスタンスを取得できる方法を提供する.
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
// Singleton singleton1 = new Singleton();
//
Singleton singleton2 = Singleton.getInstance();
}
}
/**
* @author:py
* @date:
* @description: ( )
* @version:
*/
class Singleton {
// step 2.
private static Singleton singleton = new Singleton();
//step 1.
private Singleton(){}
//step 3.
public static Singleton getInstance() {
return singleton;
}
}
(2)怠け者モード(スレッドが安全でないという問題がある可能性がある)
単一のインスタンスオブジェクトを作成すると、大量のリソースが消費されるため、怠け者モードでオブジェクトの作成を遅らせることができます.しかし、マルチスレッドが同時に使用される場合、怠け者モードを使用するとgetInstance()メソッドが同時に呼び出され、システムが複数の単一クラスインスタンスを同時に作成することになります.
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
// Singleton singleton1 = new Singleton();
//
Singleton singleton2 = Singleton.getInstance();
}
}
/**
* @author:py
* @date:
* @description: ( )
* @version:
*/
class Singleton {
// step 2.
private static Singleton singleton = null;
//step 1.
private Singleton(){}
//step 3.
public static Singleton getInstance() {
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
(3)メソッドにsynchronizedロックを追加する怠け者モード(システムボトルネックをもたらす)
synchronizedロックを追加するとスレッドのセキュリティが保証されますが、getInstance()メソッドにアクセスするたびにロックとロック解除の操作が行われます.synchronizedロックはメソッドに追加され、作用範囲が大きすぎます.一方、単一のクラスはグローバルで唯一であり、ロックの操作はシステムのボトルネックになります.
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
// Singleton singleton1 = new Singleton();
//
Singleton singleton2 = Singleton.getInstance();
}
}
/**
* @author:py
* @date:
* @description: ( +synchronized )
* @version:
*/
class Singleton {
// step 2.
private static Singleton singleton = null;
//step 1.
private Singleton(){}
//step 3.
public static synchronized Singleton getInstance() {
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
(3)二重チェックロックを追加した怠け者モード(JVM命令再編成でプログラムエラーが発生する可能性がある)
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
// Singleton singleton1 = new Singleton();
//
Singleton singleton2 = Singleton.getInstance();
}
}
/**
* @author:py
* @date:
* @description: ( + )
* @version:
*/
class Singleton {
// step 2.
private static Singleton singleton = null;
//step 1.
private Singleton(){}
//step 3.
public static Singleton getInstance() {
//
if(singleton == null){
synchronized (Singleton.class){
//
if(singleton == null){
// ,
singleton = new Singleton();
}
}
}
return singleton;
}
}
上記のコードでは、singleton=new Singleton()命令は、実際に以下のJVM命令として抽象化できます.
//Step 1.
memory = allocate();
//Step 2.
ctorInstance(memory);
//Step 3. instance
singleton = memory;
ここで,操作2は操作1に依存するが,操作3は操作2に依存しないため,JVMはそれらに対して命令最適化再ソートを行う.
命令再配置:JVMが命令を最適化し、プログラムの実行効率を高めるために、単一スレッドプログラムの実行結果に影響を与えない前提で、できるだけ並列度を高めることを指す.
JVM命令の並べ替えは次のようになります.
//Step 1.
memory = allocate();
//Step 3. instance ,
singleton = memory;
//Step 2.
ctorInstance(memory);
スレッドAがこの付与文を実行している場合、割り当てオブジェクトを初期化する前にsingleton参照に付与され、スレッドBがこのメソッドに入ってsingleton参照がnullではないと判断し、それを返して使用すると、プログラムエラーが発生します.
(4)二重検査ロックとvolatileキーワードを追加する怠け者モード
volatileキーワードを使用してsingletonフィールドを修飾すると、命令の並べ替え最適化を禁止し、JVMが関連コードの命令並べ替えを阻止し、既定の順序で命令を実行することを保証します.
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
// Singleton singleton1 = new Singleton();
//
Singleton singleton2 = Singleton.getInstance();
}
}
/**
* @author:py
* @date:
* @description: ( + +volatile )
* @version:
*/
class Singleton {
// step 2.
private static volatile Singleton singleton = null;
//step 1.
private Singleton(){}
//step 3.
public static Singleton getInstance() {
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
(5)静的内部クラスの一例パターン(推奨の書き方)
/**
* @author:py
* @date:
* @description:
* @version:
*/
public class Case_1 {
public static void main(String[] args) {
// Singleton singleton1 = new Singleton();
//
__Singleton singleton2 = __Singleton.getInstance();
}
}
/**
* @author:py
* @date:
* @description: ( )
* @version:
*/
class __Singleton {
//step 2. ,
private static class SingletonHolder{
private static __Singleton singleton = new __Singleton();
}
//step 1.
private __Singleton(){}
//step 3.
public static __Singleton getInstance() {
return SingletonHolder.singleton;
}
}
Springシングルモードソース
Springの依存注入(lazy-init方式を含む)はAbstractBeanFactoryのgetBean()メソッドで発生する.getBean()メソッド内でdoGetBean()メソッドを呼び出し、doGetBean()メソッド内で親クラスFactoryBeanRegistrySupportの親クラスDefaultSingletonBeanRegistryのgetSingleton()メソッドを呼び出してBeanの作成を行います.非lazy-init方式では、コンテナの初期化時に呼び出され、lazy-init方式では、ユーザがコンテナにbeanを最初に要求したときに呼び出されます.
getBean()、doGetBean()およびgetSingleton()メソッドのソースコードは、次のようになります.
public Object getBean(String name) throws BeansException {
return this.doGetBean(name, (Class)null, (Object[])null, false);
}
protected T doGetBean(String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = this.transformedBeanName(name);
Object sharedInstance = this.getSingleton(beanName);
Object bean;
//
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
Map var4 = this.singletonObjects;
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
getSingleton()法から,Springが依存注入を行う場合,二重チェックロックを用いた一例モードが見られる.
まず、beanインスタンスをキャッシュsingletonObjects(実際にはConcurrentHashMap)から取得します.
SpringソースD e f a ultSingletonBeanRegistryクラスではsingletonObjectsを次のように定義しています.
private final Map singletonObjects = new ConcurrentHashMap(256);
beanインスタンスがnullの場合は、キャッシュsingletonObjectsにロックをかけ、キャッシュearlySingletonObjects(実際にはHashMap)からbeanインスタンスを取得し、nullの場合はbeanを作成します.
SpringソースD e f a ultSingletonBeanRegistryクラスでは、earlySingletonObjectsを次のように定義しています.
private final Map earlySingletonObjects = new HashMap(16);
前述したシングル・インスタンス・モードがプライベート・コンストラクション・メソッドを使用してbeanを作成するのとは異なり、ここではsingletonFactory.getObject()によって特定のbeanNameに対応するObjectFactoryを返してbeanを作成する.実際には、AbstractAutowireCapableBeanFactoryのdoCreateBean()メソッドを呼び出し、BeanWrapperパッケージで作成されたbeanインスタンスを返します.
doCreateBean()メソッドの一部のコードは次のとおりです.
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
Object var7 = mbd.postProcessingLock;
synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable var17) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
}
mbd.postProcessed = true;
}
}
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
this.addSingletonFactory(beanName, () -> {
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
//
}