本文介绍java23种设计模式之单例模式
概念:
单例模式 是java中设计模式常见常用的一种
本文主要讲单例模式中 懒汉式 饿汉式 登记式 枚举等等。。
特点:
单例模式只能有一个实例
单例模式必须自己创建自己的唯一实例
单例模式给其他对象提供唯一的实例
一:懒汉式
/**
*
* 懒汉式-第一次调用时初始化
* */
public class Singleton {
//私有化静态变量
private static Singleton singleton = null;
//私有化构造
private Singleton() {
}
//提供唯一方法给外部调用
public static Singleton getSingleton() {
//没有就创建 有就直接返回
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
Singleton中私有化构造避免了实例在外部被实例化,从而提供了一个getSinglton()方法来进行调用和访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
但是问题来了,以上的单例模式并没有考虑安全性问题,在多线程下是不安全的,并发环境下很可能产生多个singleton实例对象。
要实现线程安全的话可以通过以下三种方式实现,皆对于getSingletion()方法进行的改造。
第一种: 直接在getSingleton()方法上加上同步锁 synchronized
//提供唯一方法给外部调用
public static synchronized Singleton getSingleton() {
//没有就创建 有就直接返回
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
第二种: 双重校验锁
//提供唯一方法给外部调用
public static Singleton getSingleton() {
//判断
if (singleton == null) {
//获取锁
synchronized (Singleton.class) {
//判断
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
第三种: 静态内部内
/**
* 懒汉式-第一次调用时初始化
*/
public class Singleton {
//类初始时直接初始化 依赖jvm实现单例
public static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
//私有化构造
private Singleton() {
}
//提供唯一方法给外部调用
public static Singleton getSingleton() {
return SingletonHolder.INSTANCE;
}
}
推荐第三种 既实现了线程安全 又省去了多线程性能的消耗
二:饿汉式
/**
* 饿汉式- 在类初始化就初始化实例
*/
public class Singleton {
//类初始化直接初始化实例
private static final Singleton singleton = new Singleton();
//私有化构造
private Singleton() {
}
//提供一个方法给外部访问
public static Singleton getSingleton() {
return singleton;
}
}
饿汉式在类初始化的时候直接实例化了一个final静态对象来提供使用 是不可变的 所以线程自带安全的
三:登记式模式(忽略就行)
//类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton3 {
private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
static{
Singleton3 single = new Singleton3();
map.put(single.getClass().getName(), single);
}
//保护的默认构造子
protected Singleton3(){}
//静态工厂方法,返还此类惟一的实例
public static Singleton3 getInstance(String name) {
if(name == null) {
name = Singleton3.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (Singleton3) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
//一个示意性的商业方法
public String about() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
Singleton3 single3 = Singleton3.getInstance(null);
System.out.println(single3.about());
}
}
登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。
这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。
四:枚举
/**
* 枚举 -实现单例模式
* */
public enum EnumSingleton {
//枚举元素本身就是单例
UNIQUEINSTANCE,;
//自己需要的操作
public void getSomething() {
System.out.println("UNIQUEINSTANCE = " + UNIQUEINSTANCE);
}
}
枚举单例模式 线程安全,调用效率高,不能延时加载
饿汉式和懒汉式区别
小结:
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
内容大部分引用与
https://blog.youkuaiyun.com/jason0539/article/details/23297037?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162028664716780269851153%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162028664716780269851153&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~hot_rank-1-23297037.pc_search_result_no_baidu_js&utm_term=java%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F