`
一个人旅行
  • 浏览: 90200 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

线程安全的单例模式

阅读更多
最常见的懒汉模式下的核心代码
if(instance == null) { 
    instance = new Singleton(); 
} 

如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。

寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:
public static Singleton getInstance() { 
   if(singleton == null) { 
      synchronized(Singleton.class) {  
         singleton = new Singleton(); 
      } 
   } 
   return singleton; 
} 

这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。

双重加锁检查--》终极解决方案?
public static Singleton getInstance() { 
  if(singleton == null) { 
     synchronized(Singleton.class) { 
       if(singleton == null) { 
         singleton = new Singleton(); 
       } 
    } 
  } 
  return singleton; 
} 

如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。
不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。

May be the final:
public class Singleton { 
   public final static Singleton INSTANCE = new Singleton(); 
   private Singleton() { 
         // Exists only to defeat instantiation. 
      } 
} 

这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现。

原文链接:http://www.iteye.com/topic/60179#
分享到:
评论
2 楼 wenlong0898 2013-01-18  
楼主试试运行上面的代码,验证一下文中的这段话:

不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。
1 楼 wenlong0898 2013-01-18  
public class Foo {

    private static Foo inst;

    public Foo() {

        System.out.println(Thread.currentThread().getName() + " 进入 Foo()构造函数,等待5s!");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        Thread.currentThread().yield();
        System.out.println(Thread.currentThread().getName() + " 构造函数放弃Cpu等待下一次调度!");
        
        System.out.println(Thread.currentThread().getName() + " 进入 Foo()构造函数,等待5s!");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        

        System.out.println(Thread.currentThread().getName() + " 构造完成!");

    }

    public static void main(String[] args) {

        Runnable run = new Runnable() {
            int i = 0;

            @Override
            public void run() {
                for (; i < 15; i++) {

                    System.out.println("第" + i + "秒:Foo.inst = " + Foo.inst);
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        };

        Thread t = new Thread(run);
        t.start();
        
        System.out.println("打印线程启动!");

        Foo.inst = new Foo();

    }

}

相关推荐

Global site tag (gtag.js) - Google Analytics