まず、生成された動力シングルトンパターンの概要は、自然及びアプリケーションシナリオSingletonパターンを明らかにする。その後、我々は、実装の下で2古典的なシングルスレッド環境でシングルトンパターンを与える:空腹の男のタイプと怠惰なタイプが、空腹中国風のスレッドセーフ、およびスレッドセーフではない怠惰な男を。
お気に入りの「黄金の3は」あなたを過ぎて行くの顔で仕事を見つけていない。しかし、我々は、私はあなたの助けを期待、アリのインタビューの質問や面接の経験を用意しました。
シングルトンの概要
Singletonパターン(シングルトン)、リストモードとして知られているが、一般的なデザインパターンです。このモデルを適用するには、クラスのシングルトンオブジェクトはインスタンスが1つだけ存在することを確認する必要があります。多くの場合、システム全体が、これはシステムの全体的な動作を調整する私たちを助ける、グローバルオブジェクトを持っているだけです。明らかに、例えば、サーバプログラムは、サーバ構成情報は、ファイルに格納されたコンフィギュレーションデータは、シングルトンオブジェクトの結束によって読み取られ、シングルトンオブジェクトを介して再取得サービス構成情報の過程で、他のオブジェクトこれは、複雑な環境における構成管理を簡素化します。
具体的には、コンピュータシステム、スレッドプール、キャッシュ、ログ・オブジェクトに、ダイアログボックス、プリンタは、グラフィックスドライバオブジェクトは、多くの場合、一の実施形態で設計されています。実際には、これらのアプリケーションは、多かれ少なかれExplorerの機能を持っています。例えば、各コンピュータは、複数のプリンタを持っているかもしれませんが、唯一のプリンタスプーラ(シングルトン)、プリンタに同時に出力を2つの印刷ジョブを避けるために。別の例として、各コンピュータは、複数の通信ポートを有していてもよく、システムは、同時に2つの同時コールを回避するために、通信ポート、通信ポートを管理する(シングルトン)焦点を当てるべきです。要するに、単一の実施形態のモードを選択し、長い政府を避けるために、一貫性のない状態を回避することです。
要約すると、単一モードの実施形態は、クラス、メソッド、およびシステム全体のアクセスのグローバルポイントのインスタンスを1つだけ確実にすることです。
シングルモードとシングルスレッド環境でのクラシックは、例を実装しました
シングルトンパターン23のデザインパターンは、最も簡単な様式であるのは、シングルトンパターン、種類、構造を定義してみましょうと四つの要素を導入するためにそれを使用する必要があります。
1、理論的基礎シングルトン
定義:クラスのインスタンスを1つだけ確保し、システム全体のアクセスのグローバルポイントを提供するために(システム全体にこの例を提供しました)。
タイプ:スキーマの作成
構造:
具体的には、より優れた上記のクラス図を理解するために、我々はいくつかの知識のクラス図を紹介するこの機会を取ります。
- 図クラスは、クラス名、プロパティ、メソッド、続いて三つの部分に分割されます。
- コメントの末尾に<< >>情報から始まります。
- 修飾子+は、公共表し - プライベート、#を代表して表したパッケージの保護、何も見え代表。
- 代表者が財産を強調したりする方法は静的です。
三つの要素:
- プライベートコンストラクタ;
- 独自のインスタンスプライベート静的参照を指しています。
- 例としては、値を返すためのpublic staticメソッドを所有しています。
実現の下で2、2古典的なシングルスレッド環境
2古典的なシングルスレッド環境前Singletonパターンの実装への導入では、まず即時荷重と遅延ロード二つの概念を説明する必要があります。
- ロードはすぐに:クラスのインスタンスを作成するためのイニシアチブをとるには、初期化時にロードされます。
- レイジーロード:作成したときにインスタンスを作成するために、それを実際に使用するまで、イニシアチブを取ることはありません。
シングルスレッド環境、例のタイミングに応じてシングルトンオブジェクトでは、二つの古典的な実装が存在する:一方は単一飢餓例(即時荷重)であり、怠惰な一つの実施の形態(遅延ロード)。空腹の男、シングルトンクラスの単一の例は、あなたがオブジェクトのインスタンスを作成するときにロードし、その参照に引き渡され、そして怠惰な単一の場合にのみとき、本当に使うには、オブジェクトをインスタンス化し、その参照に引き渡されます。次のようにコード例は以下のとおりです。
シングル飢え例:
// 饿汉式单例
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
我们知道,类加载的方式是按需加载,且加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
懒汉式单例:
// 懒汉式单例
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。
总之,从速度和反应时间角度来讲,饿汉式(又称立即加载)要好一些;从资源利用效率上说,懒汉式(又称延迟加载)要好一些。
3、单例模式的优点
我们从单例模式的定义和实现,可以知道单例模式具有以下几个优点:
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。
4、单例模式的使用场景
由于单例模式具有以上优点,并且形式上比较简单,所以是日常开发中用的比较多的一种设计模式,其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于以下几种:
- 有状态的工具类对象;
- 频繁访问数据库或文件的对象;
5、单例模式的注意事项
在使用单例模式时,我们必须使用单例类提供的公有工厂方法得到单例对象,而不应该使用反射来创建,否则将会实例化一个新对象。此外,在多线程环境下使用单例模式时,应特别注意线程安全问题,我在下文会重点讲到这一点。
多线程环境下单例模式的实现
在单线程环境下,无论是饿汉式单例还是懒汉式单例,它们都能够正常工作。但是,在多线程环境下,情形就发生了变化:由于饿汉式单例天生就是线程安全的,可以直接用于多线程而不会出现问题;但懒汉式单例本身是非线程安全的,因此就会出现多个实例的情况,与单例模式的初衷是相背离的。下面我重点阐述以下几个问题:
- 为什么说饿汉式单例天生就是线程安全的?
- 传统的懒汉式单例为什么是非线程安全的?
- 怎么修改传统的懒汉式单例,使其线程变得安全?
- 线程安全的单例的实现还有哪些,怎么实现?
- 双重检查模式、Volatile关键字 在单例模式中的应用
ThreadLocal 在单例模式中的应用
特别地,为了能够更好的观察到单例模式的实现是否是线程安全的,我们提供了一个简单的测试程序来验证。该示例程序的判断原理是:
开启多个线程来分别获取单例,然后打印它们所获取到的单例的hashCode值。若它们获取的单例是相同的(该单例模式的实现是线程安全的),那么它们的hashCode值一定完全一致;若它们的hashCode值不完全一致,那么获取的单例必定不是同一个,即该单例模式的实现不是线程安全的,是多例的。
public class Test {
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new TestThread();
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
}
class TestThread extends Thread {
@Override
public void run() {
// 对于不同单例模式的实现,只需更改相应的单例类名及其公有静态工厂方法名即可
int hash = Singleton5.getSingleton5().hashCode();
System.out.println(hash);
}
}
1、为什么说饿汉式单例天生就是线程安全的?
// 饿汉式单例
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
我们已经在上面提到,类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。
2、传统的懒汉式单例为什么是非线程安全的?
// 传统懒汉式单例
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
上面发生非线程安全的一个显著原因是,会有多个线程同时进入 if (singleton2 == null) {…} 语句块的情形发生。当这种这种情形发生后,该单例类就会创建出多个实例,违背单例模式的初衷。因此,传统的懒汉式单例是非线程安全的。
3、实现线程安全的懒汉式单例的几种正确姿势
同步延迟加载 — synchronized方法
// 线程安全的懒汉式单例
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
// 使用 synchronized 修饰,临界资源的同步互斥访问
public static synchronized Singleton2 getSingleton2(){
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
该实现与上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton2()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例
同步延迟加载 — synchronized块
// 线程安全的懒汉式单例
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2(){}
public static Singleton2 getSingleton2(){
synchronized(Singleton2.class){ // 使用 synchronized 块,临界资源的同步互斥访问
if (singleton2 == null) {
singleton2 = new Singleton2();
}
}
return singleton2;
}
}
该实现与上面synchronized方法版本实现类似,此不赘述。从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率仍然比较低,事实上,和使用synchronized方法的版本相比,基本没有任何效率上的提高
同步延迟加载 — 使用内部类实现延迟加载
// 线程安全的懒汉式单例
public class Singleton5 {
// 私有内部类,按需加载,用时加载,也就是延迟加载
private static class Holder {
private static Singleton5 singleton5 = new Singleton5();
}
private Singleton5() {
}
public static Singleton5 getSingleton5() {
return Holder.singleton5;
}
}
如上述代码所示,我们可以使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的做法,它与饿汉式单例的区别就是:这种方式不但是线程安全的,还是延迟加载的,真正做到了用时才初始化
当客户端调用getSingleton5()方法时,会触发Holder类的初始化。由于singleton5是Hold的类成员变量,因此在JVM调用Holder类的类构造器对其进行初始化时,虚拟机会保证一个类的类构造器在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造器,其他线程都需要阻塞等待,直到活动线程执行方法完毕。在这种情形下,其他线程虽然会被阻塞,但如果执行类构造器方法的那条线程退出后,其他线程在唤醒之后不会再次进入/执行类构造器,因为 在同一个类加载器下,一个类型只会被初始化一次,因此就保证了单例
阿里面试题
- 多个线程同时读写,读线程的数量远远大于写线程,你认为应该如何解决并发的问题?你会选择加什么样的锁?
- JAVA的AQS是否了解,它是干嘛的?
- 除了synchronized关键字之外,你是怎么来保障线程安全的?
- Tomcat本身的参数你一般会怎么调整?
- 你有没有用过Spring的AOP? 是用来干嘛的? 大概会怎么使用?
- 如果一个接口有2个不同的实现, 那么怎么来Autowire一个指定的实现?
- 如果想在某个Bean生成并装配完毕后执行自己的逻辑,可以什么方式实现?
- SpringBoot没有放到web容器里为什么能跑HTTP服务?
- SpringBoot中如果你想使用自定义的配置文件而不仅仅是application.properties,应该怎么弄?
- SpringMVC如果希望把输出的Object(例如XXResult或者XXResponse)这种包装为JSON输出, 应该怎么处理?
- 如果有很多数据插入MYSQL 你会选择什么方式?
- 如果查询很慢,你会想到的第一个方式是什么?索引是干嘛的?
- 查询死掉了,想要找出执行的查询进程用什么命令?找出来之后一般你会干嘛?
- 读写分离是怎么做的?你认为中间件会怎么来操作?这样操作跟事务有什么关系?
- 分库分表有没有做过?线上的迁移过程是怎么样的?如何确定数据是正确的?
- 你知道哪些或者你们线上使用什么GC策略? 它有什么优势,适用于什么场景?
- JAVA类加载器包括几种?它们之间的父子关系是怎么样的?双亲委派机制是什么意思?有什么好处?
- 如何自定义一个类加载器?你使用过哪些或者你在什么场景下需要一个自定义的类加载器吗?
- 堆内存设置的参数是什么?
- HashMap和Hashtable的区别。
- 实现一个保证迭代顺序的HashMap。
- 说一说排序算法,稳定性,复杂度。
- 说一说GC。
- JVM如何加载一个类的过程,双亲委派模型中有哪些方法?
- TCP如何保证可靠传输?三次握手过程?
面试心得
- 准备要充分,知识面要尽量的广,同时深度也要够。
- 面试安排上,如果不着急,尽量给自己留多时间,两天一家,及时做总结和补充。
- 心态要放平,当做一次技术交流,面试要看一部分的运气,也要看一些眼缘,有的面试官一张嘴你就能感觉到你这次面试完了。想去的公司没有面试好,不要气馁,继续加油准备。
- 简历投递方面,拉勾上投了很多经常不匹配,可能是我学历问题(自考本),有一些打击自信心,如果有同样感受的,不妨换BOSS或者其他平台。避免打击自信心。
- 写简历一定要体现自己的优势,最好能体现类似于,用到了什么技术,解决了什么问题。简历上写到的一定要胸有成竹。
- 类似于你的优势是什么,你觉得你项目中做的比较好的地方有哪些,你能给公司带来什么,这种问题心里要先想一些,免得临场发挥容易紧张说不好。
それは、よく書かれ、批判やまだあなたが助けることができるインタビューを期待している人たちの多くは、すべてのIシングルトンと、複数のスレッドの並べ替えだけでなく、アリのインタビューの質問や面接経験してくださいです!!