2面の質問:Singletonパターンを実現
タイトル:デザインクラスは、我々は唯一のクラスのインスタンスを生成することができます。
デザインパターンは、面接の過程で、オブジェクト指向プログラミングにおいて重要な役割を果たしているのために、多くの企業は、デザインモードに関連するいくつかの質問をお願いしたいです。従来のモードでは、シングルトンモードは、完全な達成するためにコードの数行のみスパンを使用することができます。そのため、シングルトンを書くことインタビューの質問の非常に一般的なタイプです。
あなたは私のコラムのデザインパターンを読めば、前に書かれ、その後、あなたはオープンこの質問の考え方だろう。
重要単一の実施形態の3つのモードがあります:第一に某个类只能有一个实例
、第二它必须自行创建这个实例
、第三它必须自行向整个系统提供这个实例
。
ここでは、その実装を見て
まず、怠惰な人間の書き込み
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if(lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
キーは、制約のみ静的メソッドの内部インスタンスを介して取得することができ、プライベートコンストラクタにあります。
しかし、このようなアプローチは、スレッドセーフでないことは明らかです。クラスの初期化の前に、複数のスレッドが、複数のスレッドがgetInstanceメソッドとlazySingletonがコールしている場合には条件が右、今回は新しいLazySingleton複数のインスタンスにつながるされている場合== nullを決定。だから、それを変更するには:
言葉遣いがDoubleCheckと呼ばれています。(lazySingleton == NULL)場合に、クラス初期化スレッドの前に複数のコードブロック場合に入力しました
条件は新しいインスタンスのうち満たされていれば、当然、それは偽、ほとんど解決された問題である場合は、この時間ロック制御は、(lazySingleton == nullの)場合は、再度決定し、他のスレッドにターンが決定されます。
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazySingleton = null;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if(lazySingleton == null) {
synchronized (LazyDoubleCheckSingleton.class){
if(lazySingleton == null) {
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}
しかし、そうであっても、いくつかの問題を解決することはできません、まだ上記のコードを向上させます。
並べ替え問題が存在しますので。並べ替えは、コンパイラの最適化技術、満足している「コンパイラの理論」されている、ここでは詳細に説明しませんが、それがいかにあなたを伝えるために。
通常、次のコード
lazySingleton = new LazyDoubleCheckSingleton();
実装がある場合は、この:
1.オブジェクトのメモリを割り当て
、オブジェクトの初期化2.
3.設定LazyDoubleCheckSingletonポイントだけで割り当てられたメモリアドレス。
しかし、コンパイラの最適化は、これは方法かもしれない
オブジェクトにメモリを割り当てるための1
だけのメモリアドレスに割り当てられた3セットLazyDoubleCheckSingletonポイント。
オブジェクトの初期化2.
問題のステップ2とステップ3と負、。(コンパイラコンパイラの最適化を提供される)
、例えば2件のスレッドがあり、スレッド名は1であり、2スレッド1入力した場合(lazySingleton == NULL)ブロックスレッド、ロック取得をしたnew LazyDoubleCheckSingleton()
実行します、クラス構造体のインスタンスをロードするとき、提供さLazyDoubleCheckSingleton点だけメモリアドレスを割り当て、そのオブジェクトが初期化されていません。(lazySingleton == nullの)がfalseの場合、問題になります使用されたとき、彼らは、使用していた、直接lazySingletonを返し、2判断を通します。
それの二つの絵を描い:
どこで次のように並べ替え:
問題を見て
もちろん、これは非常に良い改善は、揮発性の追加、無効化、並べ替えの面で開始します。セキュリティスレッドに精通していない、この記事を参照することができ、[Javaの並行処理]詳細なスレッドの安全性
private volatile static LazyDoubleCheckSingleton lazySingleton = null;
さて以上片道よりも、あなたはまた、遅延ロードの初期化クラスに基づいて静的な内部クラスの使用具体的には、解決するために、「可視性」のオブジェクトの初期化を使用することができ、名前が非常に長いですが、理解することは難しいことではありません。(このメソッドを使用して上記のコンパイラの最適化によって引き起こされる問題を心配しないでください)
JVMクラスの初期化遅延ロードと密接に関連しては、我々はの例を証明
类
ちょうどのみロード、およびリンクされて初期化されていませんされているが。
私たちは、実装を見て:
静的な内部クラスの定義、シングルトンの静的フィールドインスタンスを。getInstance間接的な買収を呼び出すためのシングルトンの必要性を取得します。
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
}
あなたは内部クラスに慣れていない場合は、こちらの記事を参照することができますJavaの内部クラスの[音量] Javaコア技術の深い理解
レイジー式プレゼンテーション右ここでは、別の実施形態のシングルモード、次の外観を達成するために
第二に、空腹中国風の書き込み
あなたの基本的な文言を表示
public class HungrySingleton {
// 类加载的时候初始化
private final static HungrySingleton hungrySingleton = new HungrySingleton();
/*
也可以在静态块里进行初始化
private static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
*/
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
饿汉式在类加载的时候就完成单例的实例化,如果用不到这个类会造成内存资源的浪费,因为单例实例引用不可变,所以是线程安全的
同样,上面的饿汉式写法也是存在问题的
我们依次看一下:
首先是序列化破坏单例模式
先保证饿汉式能够序列化,需要继承Serializable 接口。
import java.io.Serializable;
public class HungrySingleton implements Serializable {
// 类加载的时候初始化
private final static HungrySingleton hungrySingleton = new HungrySingleton();
/*
也可以在静态块里进行初始化
private static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
*/
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
我们测试一下:
import lombok.extern.slf4j.Slf4j;
import java.io.*;
@Slf4j
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
oos.writeObject(hungrySingleton);
File file = new File("singleton");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newHungrySingleton = (HungrySingleton) ois.readObject();
log.info("结果 {}",hungrySingleton);
log.info("结果 {}",newHungrySingleton);
log.info("对比结果 {}",hungrySingleton == newHungrySingleton);
}
}
结果:
结果发现对象不一样,原因就涉及到序列化的底层原因了,我们先看解决方式:
饿汉式代码中添加下面这段代码
private Object readResolve() {
return hungrySingleton;
}
重新运行,这个时候的结果:
原因出在readResolve方法上,下面去ObjectInputStream源码部分找找原因。(里面都涉及到底层实现,不要指望看懂)
在一个读取底层数据的方法上有一段描述
就是序列化的Object类中可能定义有一个readResolve方法。我们在二进制数据读取的方法中看到了是否判断
private Object readOrdinaryObject()方法中有这段代码,如果存在ReadResolve方法,就去调用。不存在,不调用。联想到我们在饿汉式添加的代码,大致能猜到怎么回事了吧。
***
另外一种情况就是反射攻击破坏单例
演示一下
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@Slf4j
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true); // 强行打开构造器权限
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
log.info("结果{}",instance);
log.info("结果{}",newInstance);
log.info("比较结果{}",newInstance == instance);
}
}
这里强行破开了private的构造方法的权限,使得能new出来一个单例实例,这不是我们想看到的。
解决方法是在构造方法中抛出异常
private HungrySingleton() {
if( hungrySingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
这个时候再运行一下
其实对于懒汉式也是有反射破坏单例的问题的,也可以采用类似抛出异常的方法来解决。
饿汉式单例与懒汉式单例类比较
- 饿汉式单例类在自己被加载时就将自己实例化。单从资源利用效率角度来讲,这个比懒汉式单例类稍差些。从速度和反应时间角度来讲,则比懒汉式单例类稍好些。
- 例えば遅延単一クラスのインスタンス、良好な最初の基準そのような制限にアクセスするために複数のスレッドを処理を有する、特にときに、リソースの初期化は、必ずしもインスタンス化を含むリソースコントローラとしてクラスシングルトンリソースの初期化、およびおそらくそのような参照の可能性が大きくなる最初に複数のスレッドが同時に存在することを意味多くの時間を過ごすためにされ、必要は、同期機構によって制御されます。
第三に、列挙
加えて、シングルトンパターンを実装することで列挙
Singletonパターンを達成するための列挙の方法を使用してプッシュの「効果的なJavaの」方法で、優れたオープンソースコードの多くでは、多くの場合、シングルトンの場所を達成するために列挙方法を見ることができ、列挙型は、継承に許可されていません同じことは、スレッドセーフであると、一度だけインスタンス化することができますが、列挙型は、コールがすぐにインスタンスがインスタンスを取得している中で、このような静的メソッドとして積極的に使用シングルトンの遅延ロード、することはできません。
//枚举类型本身是final的,不允许被继承
public enum Singleton
{
INSTANCE;
//实例变量
private byte[] data = new byte[1024];
Singleton()
{
System.out.println("I want to follow Jeffery.");
}
public static void method()
{
//调用该方法则会主动使用Singleton,INSTANCE将会被实例化
}
public static Singleton getInstance()
{
return INSTANCE;
}
}
実際のインタビューの中で、我々はパターンシングルトン列挙を示さなければならない、このように書くことができます。
public enum Singleton
{
INSTANCE;
public static Singleton getInstance()
{
return INSTANCE;
}
}
Javaの列挙は、私たちは私たちは、Javaコードに私たちを助けるために、コンパイラがバイトコードをコンパイルします物事のいくつかの操作を行い、コンパイラのヘルプが行われているものを見て、なぜ探検、他の言葉で、シンタックスシュガーのようなものですシングルトンパターンを達成するための列挙の方法を使用すると、押しの「効果的なJavaの」方法は何ですか?
次のようにオリジナルのコードは次のとおりです。
public enum EnumClass {
SPRING,SUMMER,FALL,WINTER;
}
抗コンパイルされたコードの後
public final class EnumClass extends Enum
{
public static EnumClass[] values()
{
return (EnumClass[])$VALUES.clone();
}
public static EnumClass valueOf(String name)
{
return (EnumClass)Enum.valueOf(suger/EnumClass, name);
}
private EnumClass(String s, int i)
{
super(s, i);
}
public static final EnumClass SPRING;
public static final EnumClass SUMMER;
public static final EnumClass FALL;
public static final EnumClass WINTER;
private static final EnumClass $VALUES[];
static
{
SPRING = new EnumClass("SPRING", 0);
SUMMER = new EnumClass("SUMMER", 1);
FALL = new EnumClass("FALL", 2);
WINTER = new EnumClass("WINTER", 3);
$VALUES = (new EnumClass[] {
SPRING, SUMMER, FALL, WINTER
});
}
}
静的参照ブロックについて理解していない:Javaの静的コードブロック、コードブロック構造、コンストラクタ、共通ブロックを
上記と組み合わせることで、それを理解することは非常に簡単ではありませんか?また、我々はまた、列挙列挙型クラスが継承されていることがわかりますが、それはまた、継承されていないこと、最終的です。
シングルトンのプレイ列挙型オンラインパスの次のカテゴリのより多くのがあります。
内部列挙クラスフォーム
実施例1オブジェクトの構築方法(まだ上述さ注)
public class EnumSingleton {
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM会保证此方法绝对只调用一次
Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
2.列挙定数の値は、オブジェクトのインスタンスであります
public class EnumSingleton {
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE(new EnumSingleton());
private EnumSingleton singleton;
//JVM会保证此方法绝对只调用一次
Singleton(EnumSingleton singleton){
this.singleton = singleton;
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
形のインタフェース
単一の標準のための列挙実施の形態、またはフォームで最高の書き込みインターフェイスを達成するために:
// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
void doSomething();
}
public enum Singleton implements MySingleton {
INSTANCE {
@Override
public void doSomething() {
System.out.println("I want to follow Jeffery. What about you ?");
}
};
public static MySingleton getInstance() {
return Singleton.INSTANCE;
}
}
私は尋ねました!を恐れて、あなたを恐れていないインタビューシングルトン、?