单例模式与线程安全

请看如下的单例类:

class Singleton{
  private static Singleton singleton = null;

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

 首先判断singleton是否为null,如果是就创建singleton对象,否则直接返回singleton。但是判断和创建并非原子操作,假设线程1正在执行null == singleton,判断为true,准备执行下一句new Singleton();此时线程2可能已经new了一个 Singleton了,线程1再次new了一个Singleton,出现2个Singleton与单例的设计思想不符,即单例的控制在并发情况下失效了,测试代码可直观的反应该问题:

public class MyThread extends Thread {

  public void run() {
    System.out.println(Singleton.getSingleton().toString());
  }
  public static void main(String[] args) {
    for(int i=0;i<10;i++){
      MyThread myThread = new MyThread();
      myThread.start();
    }
  }
}
class Singleton{
  private static Singleton singleton = null;

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

 输出:

Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@173a10f
Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@69b332
Singleton@69b332

 

可以在getSingleton方法前加synchronized,确保任意时刻都只有一个线程可以进入该方法。但是这样一来,会降低整个访问的速度,而且每次都要判断。

可以使用"双重检查加锁"的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。

    public class Singleton {  
        /**  
         * 对保存实例的变量添加volatile的修饰  
         */  
        private volatile static Singleton instance = null;  
        private Singleton(){  
        }  
        public static  Singleton getInstance(){  
            //先检查实例是否存在,如果不存在才进入下面的同步块  
            if(instance == null){  
                //同步块,线程安全地创建实例  
                synchronized(Singleton.class){  
                    //再次检查实例是否存在,如果不存在才真正地创建实例  
                    if(instance == null){  
                        instance = new Singleton();  
                    }  
                }  
            }  
            return instance;  
        }  
    } 
 

所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存 在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来, 就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

双重检查机制,尽可能缩小了同步块的范围。

这里解释一下为什么要判断2次,当两个线程调用getInstance方法时,它们都将通过第一重instance==null的判断,由于同步机制,这2个线程只能有一个进入,另一个排队。而此时如果没有第二重判断,则第一个线程创建了实例,而第二个实例离开队列重新获得锁,则将继续创建实例,这样就没有达到单例的目的。

 

在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时

访问 final 字段时

在创建线程之前创建对象时

线程可以看见它将要处理的对象时。

 

一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。

看看代码示例可能会更清晰一些,示例代码如下:

    public class Singleton {  
        /**  
         * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例  
         * 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载  
         */  
        private static class SingletonHolder{  
            /**  
             * 静态初始化器,由JVM来保证线程安全  
             */  
            private static Singleton instance = new Singleton();  
        }  
        /**  
         * 私有化构造方法  
         */  
        private Singleton(){  
        }  
        public static  Singleton getInstance(){  
            return SingletonHolder.instance;  
        }  
    } 
 

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致 SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的 域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值