单例模式 (DCL及volatile修饰的讲解)
单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
单例模式优点:
- 减少了内存开支,特别是一个对象需要频繁地创建,销毁时。
- 减少了性能开支,当一个对象的产生需要比较多的资源时,比如读取配置,产生其他依赖对象时。
- 可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 可以在系统设置全局的访问点,优化和共享资源访问,例如设计一个单例类,复制所有数据表的映射处理。
单例模式的使用场景:
- 要求生成唯一序列号的环境。
- 整个项目中需要一个共享访问点或者共享数据,例如一个Web页面上的计数器。
- 创建一个对象需要消耗资源过多,如要访问IO和数据库等资源。
单列模式有饿汉式和懒汉式。饿汉式是声明的同时就为该对象赋值。懒汉式指的是使用到的时候再创建。
懒汉模式
虚拟机的实现会保证:类加载会确保类和对象的初始化方法在多线程场景下能够正确的同步加锁,即饿汉式声明并赋值是原子操作,不会存在同步问题。
public class Singleton{
private static final Singleton singleton=new Singleton();
//限制产生多个对象,构造方法私有化
private Singleton(){
}
//通过该方法获得实例对象
Public static Singleton getSingleton(){
return singleton;
}
}
饿汉模式
在高并发情况下,需要注意线程同步问题,需要用到锁机制。其中效率最高,最安全的时,volatile修饰下的双重校验锁机制。
public class Singleton{
private String name;
private int age;
//volatile 修饰保证有序性,不会产生指令重排
private volatile static Singleton singleton;
private Singleton(){
name="allen";
age=20;
}
public static Singleton getSingleton(){
//第一次校验,不为空,就不需要抢夺锁,直接返回对象
if(singleton==null){
synchronized (Singleton.Class){
//第二次校验,防止在抢夺锁的等待过程中已经被已经抢到锁的线程创建完成
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
注意点
-
为什么要用synchronized关键字修饰:因为再多线程的情况下,会有多个线程去调用get方法去创建对象,导致创建的对象并不是单例的。
-
为什么要用双重校验:
-
为什么使用校验 if(singleton==null):判断是否已经创建了实例对象,确定是直接返回对象,还是用构造方法去创建对象
-
为什么不能使用单次校验:synchronized 外的第一次校验是为了防止每次都要去抢占这个锁,造成资源的浪费,只有在对象为空时,我们才会进入synchronized块中去进行创建对象。synchronized 内的第二次校验,因为在高并发的环境下,可能很多线程都经行到了第一次校验,if(singleton==null),也都判断对象为空。此时第一个线程抢到锁进入synchronized块中去进行创建对象并释放锁。在此之前已经记录第一次判断结果的线程,会继续抢占锁,并进入去执行创建对象,因此需要再经行一次判断。
-
为什么要使用volatile方法修饰:简单的说就是为了防止指令重排导致的乱序。
详解:需要先了解java对象创建的3个步骤:
第一步:加载class文件进内存,会为其创建一个对象空间,此时对象已经存在,但是里面的对象属性还没有赋值。
第二部:为对象属性进行赋值。
第三步:将这个对像和我们起的对象名建立关联。
但是jvm在执行的时候会打乱顺序,如果先经行了第三步,再进行第二步,此时对象已经不为空了,但是对象里面的属性为空,创建出来的对象是毫无意义的。线程1抢到锁进行创建,执行到第三步,线程2此时在锁外经行第一次判断。当线程1执行完第一步,第三步。线程2判断对象不为空,就不会抢锁,直接return已经创建好的对象,但是此时的对象还没完成属性赋值操作。
-
关于饿汉模式的单例创建,其实可以通过反射,以及序列化两种方式完成多对象的创建,代码是需要进一步的修改的。后面可能会继续更新。。。第一次写这种讲解性的文章,不知道关于DCL以及volatile讲解的能不能看懂