单例模式在多线程下的问题
首先一个简单的单例类:
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,无法被覆写。
参考质料:http://www.ibm.com/developerworks/cn/java/l-singleton/