单例模式,设计模式中最常用也是最简单的一种的设计模式。设计模式的作用或者说使用场合想必大家都知道,主要用在实际应用只需要实例化一次的场合,网上的例子也很多,什么打印机的例子等等。我在项目中也是用过单例模式,当时是由于项目需要,需要在应用中动态配置数据库连接池,而这种数据库连接池的配置就只需要在单例模式下进行配置。接下来和大家一起学习总结一下简单的单例模式以及在并发情况下,单例模式是如何保持线程安全的。
饿汉式单例模式是一开始就直接实例化对象,因此这种方式在并发情况当然是安全的。
package test.singleton; /** * * @author 爱琴孩 * 饿汉式单例模式 */ public class ThristySingletonDemo { private static ThristySingletonDemo thristySingletonDemo = new ThristySingletonDemo(); //这里为私有的,避免对象在外部被实例化 private ThristySingletonDemo(){ } public static ThristySingletonDemo getSingletonInstance(){ return thristySingletonDemo; } }下面开启多个线程来测试饿汉式单例模式的线程安全问题
package test.singleton; /** * @author 爱琴孩 */ public class ThristySingletonClient extends Thread { public void run(){ ThristySingletonDemo thristySingletonDemo = ThristySingletonDemo.getSingletonInstance(); System.out.println("饿汉模式对象的hash值是"+thristySingletonDemo.hashCode()); } public static void main(String[] args) { ThristySingletonClient[] thristySingletonClient = new ThristySingletonClient[10]; for(int i=0;i<thristySingletonClient.length;i++){ thristySingletonClient[i]=new ThristySingletonClient(); } for(int j=0;j<thristySingletonClient.length;j++){ thristySingletonClient[j].start(); } } }可以看到,饿汉模式的单例模式是线程安全的。
开启多个线程来测试懒汉式单例模式的线程安全问题,代码如下
package test.singleton; /** * @author 爱琴孩 */ public class ThreadDemo extends Thread{ public void run(){ LazySingletonDemo singletonDemo=LazySingletonDemo.getInstance(); System.out.println("懒汉式的对象的哈希码"+singletonDemo.hashCode()); } public static void main(String[] args) { ThreadDemo[] threadDemo =new ThreadDemo[10]; for(int i=0;i<10;i++){ threadDemo[i] =new ThreadDemo(); } for(int j=0;j<10;j++){ threadDemo[j].start(); } } }测试结果如下,显然,单例模式的效果并没有实现
那么出现这种情况,该怎么解决??大家肯定会想到用synchronized来加锁不就行了嘛,是的加锁确实能解决这个问题,但是加锁也是有窍门的,如何加,加在哪里,效率最好?? 有的小伙伴可能是直接就在方法上加锁,代码如下
package test.singleton; /** * @author 爱琴孩 *用synchronized来确保线程安全的单例模式 */ public class SafeLazySingletonDemo { private static SafeLazySingletonDemo safeLazySingletonDemo=null; private SafeLazySingletonDemo(){} public static synchronized SafeLazySingletonDemo getSingletonIntance(){ if(safeLazySingletonDemo==null){ try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } safeLazySingletonDemo=new SafeLazySingletonDemo(); } return safeLazySingletonDemo; } }开启线程测试单例模式和上面的一样,就直接看测试结果 显然,在方法上直接加synchronized 关键字是可以的,但是大家都知道,同步是比较耗费性能的,同步方法和同步关键代码块的选择,相比大家都知道该选哪一个。
具体代码如下
package test.singleton; /** * @author 爱琴孩 * 双重检查锁实现单例模式 */ public class DoubleCheckLockDemo { private volatile static DoubleCheckLockDemo doubleCheckLockDemo= null; private DoubleCheckLockDemo(){} public static DoubleCheckLockDemo getSingletonInstance(){ if(doubleCheckLockDemo==null){ try { Thread.sleep(300); synchronized(DoubleCheckLockDemo.class){ if(doubleCheckLockDemo==null){ doubleCheckLockDemo=new DoubleCheckLockDemo(); } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return doubleCheckLockDemo; } }这种同步关键代码的形式的单例模式肯定是要比直接同步方法的效果要好的。
单例模式虽然简单,加锁也没什么难点,但是要在加锁的同时考虑效率,公司的前辈说这种双检查加锁的方式,在版本比较老的jdk版本上可能不适用,而且对于并发单例模式的写法有很多,像枚举单例模式等等。。后面会继续学习总结。
参考文档 [http://blog.csdn.net/yaerfeng/article/details/7762616] [http://blog.csdn.net/cselmu9/article/details/51366946]