创建型-单例模式
了解单例
单例模式是一种创建型设计模式,它提供了一种创建对象的最佳方式;它必须保证:
- 单例类只能有一个实例化对象;
- 单例类必须创建自己的唯一实例;
- 单例类必须给其他对象提供实例;
另外:
- 它的目的是:确保一个类只用一个实例,并提供一种全局访问入口来访问该实例
- 设计思想:在获取实例的时候判断实例是否存在,如果存在,则直接返回,如果不存在则创建实例;
- 关键代码:构造方法私有化;
角色
- 单例类:包含单例实例的类
- 静态成员变量:用于存储单例的静态成员变量,final修饰防止被继承
- 获取实例方法:静态方法,用于获取单例实例
- 私有构造方法:防止外部直接实例化类
- 线程安全处理:确保多线程环境下单例创建的安全性
实现方式
饿汉式单例
特点:类一加载就实例化单例对象
public class Mgr01 {
//静态成员变量存储单例,final修饰防止被继承
private final static Mgr01 INSTANCE = new Mgr01();
//构造方法私有化,防止外部直接实例化
private Mgr01() {
}
//静态方法,用于获取单例
public static Mgr01 getInstance(){
return INSTANCE;
}
}
另一种写法,在静态代码块中实例化对象
public class Mgr02 {
private final static Mgr02 INSTANCE ;
static {
INSTANCE = new Mgr02();
}
public static Mgr02 getMgr02() {
return INSTANCE;
}
}
懒汉式单例
特点: 使用单例时才实例化对象
线程不安全写法:
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
public static Mgr03 getInstance(){
//模拟执行其他操作所用的时间
if( INSTANCE == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
INSTANCE = new Mgr03();
}
return INSTANCE;
}
}
synchronized锁获取实例静态方法,保证线程安全:
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04() {
}
public static synchronized Mgr04 getInstance(){
//模拟执行其他操作所用的时间
if( INSTANCE == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
}
双重检验锁(DCL)单例:
- 第一层null值检测是为了在已经存在单例的情况下不需要等锁提高效率,第二次null判断是为了保证单例。
- volatile关键字的作用:如果不使用volatile关键字那么,创建单例过程可能被拆分为以下几步,①为单例对象分配内存空间;②初始化单例对象;③将INSTANCE变量指向分配的内存空间。在没有volatile 关键字的情况下,步骤②和③可能会被重排序。这就可能导致其他线程在执行getInstance() 方法时,看到的 INSTANCE 变量已经被赋值,但单例对象并没有被完成初始化。
public class Mgr06 {
private static volatile Mgr06 INSTANCE;//volatile 是为了防止JVM中语句重排
private Mgr06() {
}
public static Mgr06 getInstance(){
//这个判断可以屏蔽很多操作,很多线程到这,如果已INSTANCE已经存在,可以减少下面代码的执行,提升效率
if( INSTANCE == null){
synchronized (Mgr06.class){
if(INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
}
静态内部类单例
这种方法是通过JVM保证单例,JVM在加载外部类时,只加载一次,且内部类在使用时才会加载,也就是第一次调用获取实例的方法时候才会调用。
其中,内部类私有化,内部类中的静态变量也私有化;
public class Mgr07 {
private static class MGR_07{
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance(){
return MGR_07.INSTANCE;
}
}
枚举单例
枚举单例不但可以保证单例,还可以防止反序列化,因为枚举没有构造方法。
public enum Mgr08 {
INSTANCE
}