静态内部类实现单例模式的单例模式是否有线程安全问题

面试题:线程安全的单例模式 - Let It Go - ITeye技术网站
博客分类:
面试被问到一个线程安全的单例模式问题,想拿出来讨论一下,我通常会使用的这样的写法来实现单例:
public class Singleton {
private Singleton() {}
private static Singleton instance =
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
单例的目的是为了保证运行时Singleton类只有唯一的一个实例,最常用的地方比如拿到数据库的连接,Spring的中创建BeanFactory这些开销比较大的操作,而这些操作都是调用他们的方法来执行某个特定的动作。
面试官的问题是:单例会带来什么问题?
我第一反映就是如果多个线程同时调用这个实例,会有线程安全的问题,当时就这么说了,然后他问:“怎么实现一个线程安全的单例模式呢?”
这个问题我没有回答上来,当时脑子里闪了一下如果用synchronized来锁定可能会有一些问题,至于是什么问题没有想明白,就选择没有回答。
这里请问各位高手,
1、如果不执行修改对象的操作的情况下,单单执行一个读取操作,还有没有进行同步的必要?
2、保证单例的线程安全使用synchronized会产生什么样的问题?
3、不使用synchronized,有什么方式来保证线程安全?
4、假如下次再面试遇到这种情形,用什么方式回答会使面试官感到比较满意?
--------------------------------------------------------------------------------------------------------------------------------------------------------------
感谢大家的讨论与支持,总结一下:
实际上使用什么样的单例实现取决于不同的生产环境,懒汉式也就是我在上面举得那个例子,这种方式适合于单线程程序,多线程情况下需要保护getInstance()方法,否则可能会产生多个Singleton对象的实例。
在此基础上确保getInstance()方法一次只能被一个线程调用就需要在getInstance()方法之前加上 synchronized 关键字,锁定整个方法,
public class Singleton{
private static Singleton instance=
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
但很多时候我们通常会认为锁定整个方法的是比较耗费资源的,代码中实际会产生多线程访问问题的只有 instance = new Singleton(); 这一句,
为了降低 synchronized 块性能方面的影响,只锁定instance = new Singleton(); 这一句,“weishuang”回帖中使用的就是这种方式:
public class Singleton{
private static Singleton instance=
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
instance=new Singleton();
分析这种实现方式,两个线程可以并发地进入第一次判断instance是否为空的if 语句内部,第一个线程执行new操作,第二个线程阻断,当第一个线程执行完毕之后,第二个线程没有进行判断就直接进行new操作,所以这样做也并不是安全的。
为了避免第二次进入synchronized块没有进行非空判断的情况发生,添加第二次条件判断,就像“tomorrow009”在帖子中回复的示例一样
public static Singleton getInstance(){
if(instance == null){
synchronize{
if(instance == null){
instance =
new Singleton();
这样就产生了二次检查,但是二次检查自身会存在比较隐蔽的问题,查了在DeveloperWorks上的一篇文章,对二次检查的解释非常的详细:
其实找到这篇文章之后,我的问题基本上就已经可以解决了,但是看到回帖的同学们也有一些和我一样的问题,还想把这个问题继续梳理一遍。
使用二次检查的方法也不是完全安全的,原因是 java 平台内存模型中允许所谓的“无序写入”会导致二次检查失败,所以使用二次检查的想法也行不通了。
在最后提出这样的观点:“无论以何种形式,都不应使用双重检查锁定,因为您不能保证它在任何 JVM 实现上都能顺利运行。”
"netrice"在回复中提到了使用“java5以后的volatile关键字”,用volatile关键字来声明变量,声明成 volatile 的变量被认为是顺序一致的,即,不是重新排序的。但是volatile关键字的特性并不适用于这篇帖子所讨论的问题关键。
通过上面的分析,可以看到使用懒汉式的lazy方式实现单例弯弯绕太多,在单线程编程的情况下懒汉式单例实现是没有任何问题的,如果在多线程的情况下,我们需要比较小心,对getInstances()方法加上synchronized关键字,这样虽然可能有一些性能上的牺牲,但是更加的安全。绕了这么大的一个弯,又回来了:
/* 安全的方式 1 */
public class Singleton{
private static Singleton instance=
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
提到的另外一种实现方式是这样的,放弃使用 synchronized 关键字,而使用 static 关键字:
/* 安全的方式 2 */
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
这种方式没有使用同步,并且确保了调用static getInstance()方法时才创建Singleton的引用(static 的成员变量在一个类中只有一份)。
还有“keshin”提到的方式则更加灵巧,没有使用同步但保证了只有一个实例,还同时具有了Lazy的特性(出自)
/* 安全的方式 3 */
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
上面的方式是值得借鉴的,在ResourceFactory中加入了一个私有静态内部类ResourceHolder ,对外提供的接口是 getResource()方法,也就是只有在ResourceFactory .getResource()的时候,Resource对象才会被创建,
这种写法的巧妙之处在于ResourceFactory 在使用的时候ResourceHolder 会被初始化,但是ResourceHolder 里面的resource并没有被创建,
这里隐含了一个是static关键字的用法,使用static关键字修饰的变量只有在第一次使用的时候才会被初始化,而且一个类里面static的成员变量只会有一份,这样就保证了无论多少个线程同时访问,所拿到的Resource对象都是同一个。
饿汉式的实现方式虽然貌似开销比较大,但是不会出现线程安全的问题,也是解决线程安全的单例实现的有效方式。
至于ThreadLocal,我认为还是应该由使用场景来决定。
在《Java与模式》中,作者提出:“饿汉式单例类可以在Java语言实现,但不易在C++内实现,因为静态初始化在C++里没有固定的顺序,因而静态的instance变量的初始化与类的加载顺序没有保证,可能会出问题。这就是为什么GoF在提出单例类的概念时,举的例子是懒汉式的。他们的书影响之大,以致Java语言中单例类的例子也大多是懒汉式的。实际上,本书认为饿汉式单例类更符合Java语言本身的特点。”
由此可见在应用设计模式的同时,分析具体的使用场景来选择合适的实现方式是非常必要的。
寻找问题解决过程中找的一些参考资料:
因为在精华帖中没有找到很流畅解释这个问题的内容才发了这个帖子,还是很不幸的被评为了新手帖,但如果下次有面试官问有关线程安全的单例模式问题,我想我知道该怎么回答了。
论坛回复 /
(45 / 27979)
viei 写道我一般这样写
public class Singleton{
static class SingletonHolder{
& static Singleton instance=new Singleton();
public static Singleton getInstance(){
& return Singleton.instance();
最早从google的一个叫lee的人那里学来的,现在基本都这样写
恩,这样写确实精妙,避免了对静态数据域直接赋值所带来的浪费。不用在不用的时候创建对象。
呵呵,我自己说的拗口了。
Singleton.instance();
的instance()方法哪里定义的?》》》》》》》》》》》》》》》
我一般这样写
public class Singleton{
static class SingletonHolder{
& static Singleton instance=new Singleton();
public static Singleton getInstance(){
& return Singleton.instance();
最早从google的一个叫lee的人那里学来的,现在基本都这样写
恩,这样写确实精妙,避免了对静态数据域直接赋值所带来的浪费。不用在不用的时候创建对象。
呵呵,我自己说的拗口了。
xl10230 写道tomorrow009 写道Singleton模式分两种,“懒汉”和“恶汉” 恶汉模式也就是前面很多人提到的
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
//私有化构造函数.
public static Singleton getInstance(){
由于实例在类加载时就已经创建,所以不存在线程同步问题,如果该Singleton初始化时不需要很大的开销(比如io操作/数据库连接)之类的,通常用这种办法就可以了.
某些情况下我们希望实例只有被第一次用到的时候才创建,那么这时候就使用“懒汉”模式,初学者通常会采用LZ的写法,正如面试官所说,这样会有线程同步问题,而导致多个实例被创建。如果在 getInstance()方法前面加上synchronize,又会大大降低系统性能。其实我们需要考虑的是,究竟要同步哪里? 我们只需要同步new Singleton()这个部分,保证只有一个实例被创建出来即可,而无须同步整个getInstance()方法,那么就可以考虑这样做
public static Singleton getInstance(){
if(instance == null){
synchronize{
if(instance == null){
instance =
new Singleton();
& 应该就是这样了。
顶一个double checked模式
真搞不懂,这样跟
&&& public static Singleton getInstance(){
&&&&&&&&&&& synchronize{
&&&&&&&&&&&&&& if(instance == null){
&&&&&&&&&&&&&&&&& instance =& new Singleton();
&&&&&&&&&&&&&& }
&&&&&&&&&&&&&
&&&&&&&&&&& }
&&& }
有什么区别,多此一举吗?
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
又学习了单例模式一种新的写法
通常有两种常见的策略实现单例,一如lz所言,即所谓lazy形式的。如果害怕线程安全问题,而又不想用synchronized影响性能的话,不如用另一种:
public class Singleton {&
&&& private Singleton() {}&
&&& // 载入class时立即初始化
&&& private static Singleton instance = new Singleton();&
&&& public static Singleton getInstance() {&
当然这种也有缺点,instance会立即初始化,而不管是否实际用到。:)
这个缺点根本就不是缺点。你用不到这个类为什么访问它呢,访问它获得实例必须初始化。说白了这个所谓缺点仔细想明白,你会发现这是最佳实现方式。
bencode 写道keshin 写道
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
这个好, 延迟初始化,线程安全,效率高(没有使用同步锁,而由类加载器保证) 简洁
凤舞凰杨都跟你们说了多看看书,还是有人搞出什么double check啊之类的来.
上面这个是目前最简单有效的单例方式.
就是一堆人视而不见
我也看过developerworks上讨论的double-check的问题。但据说那个问题在jdk1.5已经解决了。/blog/259991
应该可以像/developerworks/cn/java/j-dcl.html中所说的那样分析出来的,但我电脑上目前没安装Visual Studio,先留言在这,回去试出来了再说。
……楼上已经说了。
bencode 写道keshin 写道
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
这个好, 延迟初始化,线程安全,效率高(没有使用同步锁,而由类加载器保证) 简洁
凤舞凰杨都跟你们说了多看看书,还是有人搞出什么double check啊之类的来.
上面这个是目前最简单有效的单例方式.
就是一堆人视而不见
是的上面是最简单有效的 Lazy Loading Singletons实现方法,关于几种Singleton实现方法,Google工程师Bob Lee有个很好的帖子 http://crazybob.org/2007/01/lazy-loading-singletons.html。
总结成以下三点:
1. 使用Synchronized同步getInstance方法, 简单有效适合所有的JVM版本,但Lock contention带来性能开销
2. 使用Double-checked Locking 和只同步create instance的部分,同时必须声明单列变量为volatile,否则同样不是完全线程安全的。同样由于Java Memory Model的对volatile的模糊定义,这个模式无法使用在5之前的JVM。新的JMM对volatile定义更明确,compound operation (比如++, get-set)也是原子性的,所以DCL可以放心使用在Java 5中。使用在5以后版本,可以提升10%性能(bob lee测试)
3. 最快的方法还是Lazy Loading Singletongs, 它从Initialization on Demand Holder (IODH) 模式演化而来, 针对这个模式Effective Java 第48条也有很详细的描述。
最后还是要看情况来合理使用各种技巧, 很多时候其实最老土的发法一还是很好很管用的
tomorrow009 写道Singleton模式分两种,“懒汉”和“恶汉” 恶汉模式也就是前面很多人提到的
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
//私有化构造函数.
public static Singleton getInstance(){
由于实例在类加载时就已经创建,所以不存在线程同步问题,如果该Singleton初始化时不需要很大的开销(比如io操作/数据库连接)之类的,通常用这种办法就可以了.
某些情况下我们希望实例只有被第一次用到的时候才创建,那么这时候就使用“懒汉”模式
第一种方式(加载时创建实例)有什么问题?类的加载机制是第一次调用static方法时类才加载,也就是在调用getInstance()方法时Singleton才被加载,这样跟第二种的加载时间有什么不同?不都是第一次使用时才加载吗?
以上是我的困惑,希望楼下有人给我解惑
同问。
只在调用静态方法getInstance时,instance 才会初始化。至少在JDK1.6是如此的。又何必去再里面定义一个内部类,加一个静态变量,在getInstance方法里面调用此变量呢。很疑惑。
PS 越看越觉得书读的少啊。。。
Hibernate的文档时看到了关于使ThreadLocal管理多线程访问的部分。具体代码如下
1.& public static final ThreadLocal session = new ThreadLocal();
2.& public static Session currentSession() {
3.&&&&& Session s = (Session)session.get();
4.&&&&& //open a new session,if this session has none
5.&& if(s == null){
6.&&&&& s = sessionFactory.openSession();
7.&&&&& session.set(s);
8.&& }
&&&&&
9. }
我们逐行分析
1。 初始化一个ThreadLocal对象,ThreadLocal有三个成员方法 get()、set()、initialvalue()。
&&& 如果不初始化initialvalue,则initialvalue返回null。
3。session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session(相当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己的s(数据库连接)。
5。如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是行6。
6。创建一个数据库连接实例 s
7。保存该数据库连接s到ThreadLocal中。
8。如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。
LZ这个也没谈到Hibernate 你们干嘛要说Hibernate中的ThreadLocal啊 - - 崩溃啊啊!!!!!
就是一个管理Sessin的类
keshin 写道
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
public static Resource getResource() {
return ResourceFactory.ResourceHolder.
static class Resource {
java concurrency in practice中建议的方式
这个好, 延迟初始化,线程安全,效率高(没有使用同步锁,而由类加载器保证) 简洁
凤舞凰杨都跟你们说了多看看书,还是有人搞出什么double check啊之类的来.
上面这个是目前最简单有效的单例方式.
就是一堆人视而不见
tomorrow009 写道Singleton模式分两种,“懒汉”和“恶汉” 恶汉模式也就是前面很多人提到的
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){
//私有化构造函数.
public static Singleton getInstance(){
由于实例在类加载时就已经创建,所以不存在线程同步问题,如果该Singleton初始化时不需要很大的开销(比如io操作/数据库连接)之类的,通常用这种办法就可以了.
某些情况下我们希望实例只有被第一次用到的时候才创建,那么这时候就使用“懒汉”模式,初学者通常会采用LZ的写法,正如面试官所说,这样会有线程同步问题,而导致多个实例被创建。如果在 getInstance()方法前面加上synchronize,又会大大降低系统性能。其实我们需要考虑的是,究竟要同步哪里? 我们只需要同步new Singleton()这个部分,保证只有一个实例被创建出来即可,而无须同步整个getInstance()方法,那么就可以考虑这样做
public static Singleton getInstance(){
if(instance == null){
synchronize{
if(instance == null){
instance =
new Singleton();
& 应该就是这样了。
顶一个double checked模式
& 上一页 1
xiaozhi7616
浏览: 134219 次
来自: 北京
very good,
jiht594 写道楼主你好:这个第一段js代码+那一行htm ...
楼主你好:这个第一段js代码+那一行html代码我试的时候为什 ...
多谢 还有别的方法 例如取消linksbuilder 不 ...
嗯,写的不错,明白啥意思了。。。。。。就是和配置助手里面配的那 ...线程安全的单例模式
面试的时候,常常会被问到这样一个问题:请您写出一个单例模式(Singleton
Pattern)吧。好吧,写就写,这还不容易。顺手写一个:
public&final&class&EagerSingleton
&&&&private&static&EagerSingleton&singObj&=&new&EagerSingleton();&&
&&&&private&EagerSingleton(){
&&&&public&static&EagerSingleton&getSingleInstance(){
&return&singObj;
& & 这种写法就是所谓的饥饿模式,每个对象在没有使用之前就已经初始化了。这就可能带来潜在的性能问题:如果这个对象很大呢?没有使用这个对象之前,就把它加载到了内存中去是一种巨大的浪费。针对这种情况,我们可以对以上的代码进行改进,使用一种新的设计思想——延迟加载(Lazy-load Singleton)。
public&final&class&LazySingleton
&&&&private&static&LazySingleton&singObj&=
&&&&private&LazySingleton(){
&&&&public&static&LazySingleton&getSingleInstance(){
& if(null ==&singObj&)&singObj =
new LazySingleton();
& return&singObj;
& & 这种写法就是所谓的懒汉模式。它使用了延迟加载来保证对象在没有使用之前,是不会进行初始化的。但是,通常这个时候面试官又会提问新的问题来刁难一下。他会问:这种写法线程安全吗?回答必然是:不安全。这是因为在多个线程可能同时运行到第九行,判断singObj为null,于是同时进行了初始化。所以,这是面临的问题是如何使得这个代码线程安全?很简单,在那个方法前面加一个Synchronized就OK了。
public&final&class&ThreadSafeSingleton
&&&&private&static&ThreadSafeSingleton&singObj&=&null;&&
&&&&private&ThreadSafeSingleton(){
&&&&public&static
Synchronized&ThreadSafeSingleton&getSingleInstance(){
& if(null&==&singObj&)&singObj
= new&ThreadSafeSingleton();
&&return&singObj;
写到这里,面试官可能仍然会狡猾的看了你一眼,继续刁难到:这个写法有没有什么性能问题呢?答案肯定是有的!同步的代价必然会一定程度的使程序的并发度降低。那么有没有什么方法,一方面是线程安全的,有可以有很高的并发度呢?我们观察到,线程不安全的原因其实是在初始化对象的时候,所以,可以想办法把同步的粒度降低,只在初始化对象的时候进行同步。这里有必要提出一种新的设计思想——双重检查锁(Double-Checked Lock)。
public&final&class&DoubleCheckedSingleton
&&&&private&static&DoubleCheckedSingletonsingObj&=&null;&&
&&&&private&DoubleCheckedSingleton(){
&&&&public&static&DoubleCheckedSingleton&getSingleInstance(){
& if(null&==&singObj&)
&&Synchronized(DoubleCheckedSingleton.class){
& &if(null&==&singObj)
& &singObj =
new&DoubleCheckedSingleton();
&return&singObj;
&这种写法使得只有在加载新的对象进行同步,在加载完了之后,其他线程在第九行就可以判断跳过锁的的代价直接到第15行代码了。做到很好的并发度。
&至此,上面的写法一方面实现了Lazy-Load,另一个方面也做到了并发度很好的线程安全,一切看上很完美。这是,面试官可能会对你的回答满意的点点头。但是,你此时提出说,其实这种写法还是有问题的!!问题在哪里?假设线程A执行到了第9行,它判断对象为空,于是线程A执行到第12行去初始化这个对象,但初始化是需要耗费时间的,但是这个对象的地址其实已经存在了。此时线程B也执行到了第九行,它判断不为空,于是直接跳到15行得到了这个对象。但是,这个对象还没有被完整的初始化!得到一个没有初始化完全的对象有什么用!!关于这个Double-Checked
Lock的讨论有很多,目前公认这是一个Anti-Pattern,不推荐使用!所以当你的面试官听到你的这番答复,他会不会被Hold住呢?
&那么有没有什么更好的写法呢?有!这里又要提出一种新的模式——Initialization on Demand
Holder.&这种方法使用内部类来做到延迟加载对象,在初始化这个内部类的时候,JLS(Java
Sepcification)会保证这个类的线程安全。这种写法最大的美在于,完全使用了Java虚拟机的机制进行同步保证,没有一个同步的关键字。
public&class&Singleton
&&&&private&static&class&SingletonHolder&&&&
& &&public&final&static&Singleton&instance&=&new&Singleton();&&&&
&&&&public&static&Singleton&getInstance()&&&&
&&&&&&&&return&SingletonHolder.&&&&
至此,本文完。提供一些链接For your reference:
Double-Checked Lock:
Initialzation on Demand Holder:&
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。单例模式与多线程_ASP.NET技巧_动态网站制作指南
单例模式与多线程
来源:人气:28
单例模式都不陌生,有饿汉式单例、懒汉式单例等。
饿汉式单例
饿汉式单例:
public class Singleton {
ivate static Singleton instance = new Singleton();
public static Singleton getInstance(){
在类第一次加载的时候,单例就完成了初始化。下面来验证饿汉式单例的线程安全性:
public class MyThread extends Thread{
public void run() {
System.out.println(Singleton.getInstance().hashCode());
public class Test {
public static void main(String[] args) throws Exception {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
运行得到:
三次得到的 hashcode() 返回值都一样。
结论:饿汉式单例在类第一次加载的时候完成初始化,是线程安全的。
懒汉式单例
懒汉式单例:
public class Singleton {
private static Singleton instance =
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
运用了延迟加载,在需要的时候进行初始化。
然而 1、2、3 整体不具有原子性,所以懒汉式单例应该不是线程安全的。
下面证明懒汉式单例是非线程安全的:
public class MyThread extends Thread{
public void run() {
System.out.println(Singleton.getInstance().hashCode());
public class Test {
public static void main(String[] args) throws Exception {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
运行得到:
结论:懒汉式单例运用延迟加载在需要时候进行初始化,保证了特定情况下其性能要优于饿汉式单例。然而它却是非线程安全的。
双重检查锁定机制
我们尝试修改代码,目的是把懒汉式单例修改成线程安全的。
第一次尝试
为 getInstance() 加锁:
public class Singleton {
private static Singleton instance =
synchronized public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
运行得到:
这样修改可以保证线程安全性;但由于锁的独占性,多个线程频繁调用 getIntance() 很可能会阻塞,效率低下。
第二次尝试
继续缩小同步块的范围:
public class Singleton {
private static Singleton instance =
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
运行得到:
hashcode() 返回值都一样。
这种方法基本等同于第一次尝试。
第三次尝试
继续缩小同步块的范围:
public class Singleton {
private static Singleton instance =
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
运行得到:
相比前两次尝试,第三次尝试执行效率会有明显提升;但是破坏了原子性,不是线程安全的,得到的可能不是单例。
第四次尝试
双重检查锁定机制(double check lock)(DCL):
public class Singleton {
private static Singleton instance =
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
运行得到:
hashcode() 返回值都一样。
此方案综合了前面几次尝试的优点:
锁同步保证了原子性,保证线程安全性;
第一次空检测对提升性能起到了很大的作用。
但是真的实现了线程安全吗?并没有。
volatile 修正 DCL
在 DCL 基础上,为变量 instance 加上 voltile 关键字,才算是真正的实现了线程安全:
public class Singleton {
private volatile static Singleton instance =
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
运行得到:
hashcode()返回值都一样。
Q:回头来看,为什么必须加 volatile 关键字呢?
结论:懒汉式单例,经过基于 volatile 的 DCL 进行改造,能够具有线程安全性。
volatile + synchronized 实现了线程安全性。
DCL 的第一个空检测很大程度上优化了性能
final 修正 DCL
增加一个 final 域同样能解决问题:
public class Singleton {
private static Singleton instance =
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
private Singleton(){
final 域的内存语义
写内存语义:在构造函数内对一个 final 域的写入,与随后将对象引用赋值给引用变量,这两个操作不能重排序;
实现原理:在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障;
写内存语义可以确保在对象的引用为任意线程可见之前,final 域已经被初始化过了。
读内存语义:初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作不能重排序;
实现原理:在读 final 域之前插入一个 LoadLoad 屏障。
读内存语义可以确保如果对象的引用不为 null,则说明 final 域已经被初始化过了。
总之,final 域的内存语义提供了初始化安全保证:只要 this 引用没有在构造函数中“逸出”,不需要同步就可以保证任意线程看到的都是初始化后的值。
注意:此时 Singleton 并非不可变,引入 final 域的目的是能够安全地初始化。
静态内部类实现单例模式
public class Singleton {
private static class Holder{
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return Holder.
调用 getInstance() 导致 Holder 类被装载,Holder 对应的的 Class 类型的对象自动创建,JVM 会获得这个 Class 对象初始化锁,这个锁可以同步多个线程对同一个类的初始化。因此指令重排还是可能发生的,但是并不影响获得初始化锁的下一个线程,因为下一个线程进来的时候,上个线程已经完成了类的初始化。
看图更形象一些:
可能得到的执行时序:
结论:静态内部类实现的单例模式能够保证线程安全,同时具有延迟加载特性。而且代码够简洁哦。
优质网站模板

我要回帖

更多关于 静态内部类 单例 的文章

 

随机推荐