单例模式在多线程中的安全问题

前言:

Java的单例模式是一种常见的设计模式
单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
单例模式的适用场景:
1.对于那种经常实例化但是过一会儿就被销毁的对象适合使用单例模式。
2.对于创建对象需要消耗很多资源的对象。如:数据库连接池对象,线程池对象等
3.只需要一个对象保证全局的一致性的。如:Android中Application对象,网站的计数器等。

实现单例:

public class Singleton {
  private Singleton singleton;

  private Singleton() {

  }

  public Singleton getInstance() {
      if (singleton == null) {
          singleton = new Singleton();
      }
      return singleton;
  }
}

上述代码潜藏危机:
在单线程中看似是没有什么问题的,但如果放在多线程的环境中就会有问题了。假如有两个线程同时访问getInstance方法,如果期中一个线程刚进入if (singleton == null){}里面,这个时候另一个线程恰好也访问这个方法,并且完成创建了一个实例,那个刚刚挂起的那个线程继续运行的话就会再创建一个实例。

方法改进:

双重检查锁定:

public Singleton getInstance() {
      if (singleton == null) {
          synchronized (Singleton.class){
              if(singleton == null){
                  singleton = new Singleton();
              }
          }
      }
      return singleton;
  }

这样只在构造实例代码的时候加锁,对程序的性能影响小。而且只要实例化完成之后,后面基本就不会进入这个同步代码块了。
把懒汉式单例改为饿汉式单例适用静态内部类的方式实现:

public class Singleton {  
    private static class LazyHolder {  
       private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
       return LazyHolder.INSTANCE;  
    }  
}  

这种方法会在当我们getInstance()时,类加载器会去加载Nested,然后实例化Singleton的实例。这样同样能够既实现了线程安全,又避免了同步带来的性能影响。

展开阅读全文

没有更多推荐了,返回首页