本篇博客对单例模式的饿汉式、懒汉式应用在多线程下是否存在安全隐患及其解决方法进行细节讲述。面试
单例模式设计模式
定义:确保一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。安全
类型: 建立类模式多线程
单例模式是一种经常使用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。经过单例模式能够保证系统中一个类只有一个实例并且该实例易于外界访并发
问,从而方便对实例个数的控制并节约系统资源。若是但愿在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。函数
要点:主要有三个,一是某个类只能有一个实例;二是它必须自行建立这个实例;三是它必须自行向整个系统提供这个实例。this
注意:如何保证对象的惟一性?spa
单例的步骤:线程
单例模式中的饿汉式(又叫单例设计模式)、懒汉式(又叫延迟加载设计模式)。设计
1 //饿汉式(开发时经常使用) 2 class Single{//类一加载,对象就已经存在了 3 private static final Single S= new Single(); 4 private Single();//私有化该类的构造函数,这样其它类就不可以再用new方式建立对象了。 5 public static Single getInstance(){ 6 return S;//返回一个本类对象,由于该方法是静态的,静态的方法访问的只能是静态的S,因此Single S = new Single()必须是静态的。 7 } 8 } 9 10 class SingleDemo{ 11 public static void main(String[] args){ 12 Single ss = Single.getInstance();//由于Single类的构造函数已经private(私有化了),因此此处只能用 类名.函数名 调用getInstance方法。 13 //可是类名调用有前提:必须是静态的方法.因此getInstance是静态方法,要有static修饰, 14 //而该方法中返回一个已经建立好的对象ss,保证只有一个对象. 15 } 16 }
1 //懒汉式(延迟加载形式)(面试常被问到) 2 class Single{//类一加载,只是先建立引用,只有调用了getInstance方法,才会建立对象 3 private static Single S = null; 4 private Single(); 5 public static Single getInstance(){ 6 if(S==null){ 7 S = new Single(); 8 return S; 9 } 10 } 11 } 12 //若是后期被多线程并发访问时,保证不了对象的惟一性,存在安全隐患,若是改后,效率下降 13 class SingleDemo{ 14 public static void main(String[] args){ 15 Single ss = Single.getInstance(); 16 } 17 }
问题1. 分析二者在多线程并发访问状况下存在的安全隐患?
说到这里讨论多线程并发访问下存在的安全隐患,就必需要了解一下,线程安全问题产生的缘由是什么?
当一个线程在执行操做共享数据的多行代码过程当中,其它线程参与了运算就会致使线程安全问题的产生。
解决思路:就是将多条操做共享数据的代码封装起来,当有线程在执行这些代码的时候,其余线程不可参与运算。必需要当前线程把这些代码都执行完毕后,其余线程不可参与运算。
(1)在Java中,用同步代码块就能够解决这个问题
同步代码块格式: synchronized(对象){
须要被同步的代码块
}
同步的前提:同步中必须有多个线程并使用同一把锁
同步的好处:解决了线程的安全问题
同步的弊端:会相应下降效率,由于线程外的其它线程都会判断同步锁。
(2)同步函数也能够解决问题:将synchronized放到方法中修饰
(3)同步函数与同步代码块的区别?
同步函数使用的锁是固定的this。同步代码块使用的锁是任意的对象(建议使用)
(4)静态的同步函数使用的琐是:该函数所属字节码文件对象,可用当前 类名.class 表示。
对于安全隐患分析:
在饿汉式中(不存在安全隐患),当遇到多线程并发访问时,getInstance加载到线程的run()方法中,返回的对象S是共享数据,由于操做共享数据的代码只有一句,因此不会涉及到线程安全问题。
在懒汉式中(存在安全隐患),共享数据是S,且操做共享数据的代码有多条,当其中一个线程A在执行期间,执行到S=new Single();前(即执行完if(S==null),但尚未执行S=new Single()时 ),忽然被切换执行权,致使下一条线程B开始执行,一直到线程B建立完对象S后,线程A从新得到执行权,此时线程A又会再次执行S=new Single();再次建立对象,这样线程A、线程B各自建立了一个对象,致使对象不惟一,也就不能是单例了!
2. 存在的安全隐患如何解决?
(1)首先解决懒汉式中多线程并发访问时存在的对象不惟一的安全隐患!(即在getInstance()方法上用synchronized修饰)
1 //懒汉式(延迟加载形式)(面试常被问到) 2 class Single{//类一加载,只是先建立引用,只有调用了getInstance方法,才会建立对象 3 private static Single S = null; 4 private Single(); 5 public static synchronized Single getInstance(){ 6 if(S==null){ 7 S = new Single(); 8 return S; 9 } 10 } 11 } 12 13 class SingleDemo{ 14 public static void main(String[] args){ 15 Single ss = Single.getInstance(); 16 } 17 }
(2)问题2. 问题又来了,由于使用同步函数,虽然解决了安全隐患,可是一样的下降了效率。那么是什么缘由致使多线程并发访问的效率下降了呢?
分析:由于第一次建立完对象后,以后的线程每一次获取对象都要先判断同步锁,所以致使效率下降。
问题3. 懒汉式在线程安全前提下如何提升效率?
解决思路:不用同步函数中的同步,改用同步代码块中的同步,上代码
1 //懒汉式(延迟加载形式)(面试常被问到) 2 class Single{//类一加载,只是先建立引用,只有调用了getInstance方法,才会建立对象 3 private static Single S = null; 4 private Single(); 5 public static Single getInstance(){ 6 if(S==null){//此处多加了一次判断 7 synchronized(Single.class){//使用同步代码块,此处注意使用Single.class锁,由于getInstance()是静态方法,因此只能用Single.class .而this.getClass()锁是用于非静态的方法。 8 if(S==null){ 9 S = new Single(); 10 } 11 } 12 return S; 13 } 14 } 15 } 16 17 class SingleDemo{ 18 public static void main(String[] args){ 19 Single ss = Single.getInstance(); 20 } 21 }
上述代码中,多加了一次判断是为了解决效率问题,这样当线程A建立完对象后,线程B在运行时首先判断S==null,此时由于线程A已经建立完对象,因此线程B就不在执行建立对象操做。使用同步锁是为了解决安全问题。(以上都是以程序中只有线程A、线程B来解释的,再多线程原理也是同样的)
结论:因此说真正程序开发时,多数用的是饿汉式;懒汉式当多线程并发访问时,保证不了对象的惟一性(多线程切换执行权执行程序,致使对象不惟一),虽通过修改保证安全,可是其效率相应下降。