疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

单例模式在多线程下的问题


 

   

  首先一个简单的单例类:

  复制代码

  public class Logger {

  private static Logger log = null;

  // 构造函数私有化

  private Logger() {

  }

  public static Logger getLogger() {

  if (log == null) {

  log = new Logger();

  }

  return log;

  }

  }

  复制代码

  该类当放入多线程的环境中,肯定 就会出现问题,如何解决?

  1,第一种方式:在方法getLogger上加上synchronized关键字:

  复制代码

  public static synchronized Logger getLogger(){

  if(log == null){

  log = new Logger();

  }

  return log;

  }

  复制代码

  缺点:synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降。

  原因:每次调用getInstance(),都要对对象上锁。

  2,第二种方式:synchronized关键字锁住if方法:

  复制代码

  public static Logger getLogger(){

  synchronized (log) {

  if(log == null){

  log = new Logger();

  }

  }

  return log;

  }

  复制代码

  该方式的问题:

  在Java指令中创建对象和赋值操作是分开进行的,也就是说log = new Logger();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Logger实例分配空间,然后直接赋值给log成员,然后再去初始化这个Logger实例。这样就可能出错。

  以A、B两个线程为例:

  1,A、B线程同时进入了第一个if判断

  2,A首先进入synchronized块,由于log 为null,所以它执行log = new Logger();

  3,由于JVM内部的优化机制,JVM先画出了一些分配给Logger实例的空白内存,并赋值给log 成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

  4,B进入synchronized块,由于log 此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

  5,此时B线程打算使用Logger实例,却发现它没有被初始化,于是错误发生了。

  解决该问题:

  方案1:创建一个内部类:

  复制代码

  public class Logger {

  private static Logger log = null;

  //构造函数私有化

  private Logger(){}

  //创建一个私有的静态内部类

  private static class LoggerFactory{

  private static Logger logger = new Logger();

  }

  public static Logger getLogger(){

  return LoggerFactory.logger;

  }

  }

  复制代码

  方案2:把创建对象和获取对象分开:

  复制代码

  public class Logger2 {

  private static Logger2 log = null;

  //构造函数私有化

  private Logger2(){}

  public synchronized void setLogger(){

  if(log == null){

  log = new Logger2();

  }

  }

  public Logger2 getLogger(){

  if(log == null){

  setLogger();//初始化log

  }

  return log;

  }

  }

  复制代码

  方案3:采用“影子实例”同步单例对象属性的同步跟新:

  复制代码

  public class Logger {

  private static Logger log = null;

  private Vector properties = null;

  public Vector getProperties() {

  return properties;

  }

  public static Logger getLogger(){

  if(log == null){

  syncInit();

  }

  return log;

  }

  public static synchronized void syncInit(){//log对象初始化

  if(log == null){

  log = new Logger();

  }

  }

  // 替换掉原有log里面的影子properties

  public void updatePropertis() {

  Logger log1 = new Logger();

  properties = log1.getProperties();

  }

  // 构造函数私有化

  private Logger() {

  //这里模拟从服务器里面读取配置信息,赋值给properties改对象

  }

  }

  复制代码

  采用类的静态方法,实现单例模式的效果和不使用静态方法实现的单例模式的区别:

  1,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)

  2,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。

  3,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。