单例模式
简介
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
一、饿汉式
单例对象已经创建好了,直接取就完事了
/**
* 单例---饿汉模式
*/
class Res{
private Res(){}
private static final Res res = new Res();
public static Res getInstance(){
return res;
}
}
二、懒汉式
需要单例对象的时候才去创建
单线程下安全,多线程不安全
class Res2{
private Res2(){}
private static Res2 res;
public static Res2 getInstance(){
if(null == res){
res = new Res2();
}
return res;
}
}
多线程下
- 直接使用同步锁
- 双重校验锁
- 双重校验锁【volatile关键字
1. 直接使用同步锁
存在的问题:这样写的话,每个线程来都要竞争锁,可能造成大量阻塞
class Res2{
private Res2(){}
private static Res2 res;
public synchronized static Res2 getInstance(){
if(null == res){
res = new Res2();
}
return res;
}
}
2. 双重校验锁
存在的问题:因为
res = new Res2()
不是原子操作,需要
- 分配内存
- 初始化对象
- 将引用指向对象地址
JVM可能会进行指令重排,比如 1->3->2。即:分配内存后,直接将引用
res
指向内存地址这时候其他线程来判断
res!=null
,就直接把未初始化对象的res给拿去用了。造成错误
class Res2{
private Res2(){}
private static Res2 res;
public static Res2 getInstance(){
if(null == res){
synchronized (Res2.class){
if(null == res){
// 不是一个个原子操作---可能出现指令重排
// 1.分配内存 2.初始化对象 2.将这个引用res指向这个对象
// 正常顺序:1-2-3 重排:1 3 2
// 多线程情况下,就存在其他线程拿到的是一个空对象
res = new Res2();
}
}
}
return res;
}
}
如何解决?volatile
关键字
作用:防止指令重排
class Res2{
private Res2(){}
// 使用volatile防止指令重排
private volatile static Res2 res;
public static Res2 getInstance(){
if(null == res){
// 多个线程进入,再判断是否持有锁
synchronized (Res2.class){
if(null == res){
res = new Res2();
}
}
}
return res;
}
}
三、静态内部类
使用静态内部类创建单例对象
线程安全,类加载过程由类加载器负责加锁,从而保证线程安全。并且类只加载一次
/**
* 静态内部类实现单例
* 多线程:安全
*/
class Res4{
private Res4(){}
private static class Res5{
private static Res4 res = new Res4();
}
public static Res4 getInstance(){
return Res5.res;
}
}
四、枚举创建单例
上面3种创建单例的方法都可以被反射破坏,但不能使用反射创建枚举对象
报:
Cannot reflectively create enum objects
/**
* 枚举实现单例:
* 说明:上面2种模式,都可以通过暴力反射获取第二个对象。破坏单例模式
* 但枚举类不可被反射破坏
*/
enum Res3{
SUMMER;
public static Res3 getInstance(){
return Res3.SUMMER;
}
// 默认构造函数:(String,int)
}
反射创建测试:
public class Demo1 {
public static void main(String[] args) throws Exception {
Res3 res1 = Res3.getInstance();
Res3 res2 = Res3.getInstance();
System.out.println(res1+":"+res2);
Constructor<? extends Res3> constructor = res1.getClass().getDeclaredConstructor(String.class,int.class);
// 取消安全检查
constructor.setAccessible(true);
// 报:Cannot reflectively create enum objects
// 无法反射创建枚举对象
Res3 res3 = constructor.newInstance();
}