目录
单例模式实现的四种方式:饿汉、懒汉、静态内部类、枚举
饿汉
由于使用了static关键字,所以类加载时就会创建单例对象
优点:避免了多线程下线程安全的问题(只会在类加载时创建一次)
缺点:在类加载时就创建了对象,会出现如果用不上该对象从而浪费内存空间的情况
public class Instance {
/**
* 也可以吧创建对象放在static静态块中
*/
private static Instance instance = new Instance();
/**
* 私有构造
*/
private Instance() {
}
/**
* 获取实例
*
* @return
*/
public static Instance getInstance() {
return instance;
}
}
懒汉-非线程安全
创建时机是第一次被调用,延时加载
优点:需要的时候才会创建实例,节省内存
缺点:多线程并发获取单例对象的情况下回创建多个对象,线程不安全
public class Instance {
private static Instance instance;
/**
* 私有构造
*/
private Instance() {
}
/**
* 获取实例
*
* @return
*/
public static Instance getInstance() {
if (null == instance) {
instance = new Instance();
}
return instance;
}
}
懒汉-线程安全
添加synchronized来保证线程安全
优点:需要的时候才会创建实例,节省内存
缺点:解决了线程安全问题,但是在高并发的情况下性能不高
public class Instance {
private static Instance instance;
/**
* 私有构造
*/
private Instance() {
}
/**
* 使用synchronized关键字对方法加标记,串行调用
* 获取实例
*
* @return
*/
public static synchronized Instance getInstance() {
if (null == instance) {
instance = new Instance();
}
return instance;
}
}
懒汉-双重检查锁机制
使用双重检查锁避免过多的同步,第一次的检查是无锁的,如果对象已经创建则直接返回了。
重点在于第二次的同步锁校验和volatile关键字,Java中new Instance()并不是一条原子指令,通过javap -c 查看字节码时可以看到创建对象一共分了三个步骤
1.给分配对象内存
2.调用构造器方法,执行初始化
3.将对象引用赋值给变量为什么要使用volatile:
当JVM实际运行时,又有可能会对2和3指令进行重排序(因为2和3依赖于1所以1不会重排),指令重排不了解的自行百度,当有两个线程A、B同时调用获取实例方法时,第一重判断 ①都判断为空,同时进入到获取同步锁 ②阶段,假设有A获取到锁,B线程则阻塞等待,A线程则去创建对象后释放锁,由于创建对象需要经过以上三个步骤,假设new Instance()执行到第3个步骤时(还未执行第2个步骤),由于锁已经释放,B线程获取锁执行第二重判断 ③,这时对象已经不为空(已经分配了内存)了,则可以自由访问该对象,然而这时对象并没有被初始化(A线程还未执行第2个步骤),这个时候B线程使用该对象时则会出现异常,使用volatile关键字可以保证可见性和防止指令重排序。
优点:需要的时候才会创建实例,节省内存,锁粒度比较小
缺点:在某种程度上来说解决了性能问题,但是使用时需要了解对象创建的过程和volatile关键字原理,另外Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败。
public class Instance {
/**
* 使用volatile关键字修饰
* 保证可见性和防止指令重排序
*/
private volatile static Instance instance;
/**
* 私有构造
*/
private Instance() {
}
/**
* 双重检查锁
* 获取实例
*
* @return
*/
public static Instance getInstance() {
// 第一重判断 ①
if (null == instance) {
// 获取同步锁 ②
synchronized (Instance.class) {
// 第二重判断 ③
if (null == instance) {
instance = new Instance();
}
}
}
return instance;
}
}
静态内部类(推荐使用)
静态内部类实现单例某种意义上来说也是数据懒汉类,实现了延时加载的同时也没有线程安全性的问题
优点:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存。
缺点:第一次创建对象时需要重新加载子类,在某种程度上影响了执行效率
public class Instance {
/**
* 私有构造
*/
private Instance() {
}
/**
* 使用静态内部类获取
*
* @return
*/
public static Instance getInstance() {
return InstanceHolder.instance;
}
private static class InstanceHolder {
/**
* 内部类持有外部类对象
*/
static Instance instance = new Instance();
}
}
枚举类
枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式。
优点:枚举类型是线程安全的,并且只会装载一次、天然支持防止反序列化重新创建新的对象。
缺点:某种意义上来说我没有用过
public enum Instance {
INSTANCE;
/**
* 添加业务操作
*/
public void toDo() {
}
}
本期到这里啦,写的不对的地方巨佬们多多指点,喜欢的话来一个一键三连吧