单例模式
Java最简单的设计模式之一?
属于创建型模式,指一个类负责创建自己唯一的一个对象,对外提供一个的可以直接访问该对象的方式,而不需要实例化该类对象。
1.只能有一个实例
2.自己创建自己的唯一实例
3.必须对外提供该实例
安全是指普通情况下多线程安全,但除枚举方式外,均可通过反射获取多个对象
饿汉模式-多线程安全
public class HungryMan {
private static HungryMan hungryMan=new HungryMan();
private HungryMan(){
}
public static HungryMan getInstance(){
return hungryMan;
}
}
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
懒汉模式-多线程不安全
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
public static LazyMan getInstance(){
if (lazyMan==null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
public static void main(String[] args) {
System.out.println(LazyMan.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(LazyMan.getInstance());
}).start();
}
}
运行如下,众所周知,当多个线程同时在lazyMan对象即将赋值时失去CPU调度权,之后继续运行,就会有多个不同的对象产生
懒汉模式-多线程安全
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
//加锁
public static synchronized LazyMan getInstance(){
if (lazyMan==null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
优点:这种实现就是在方法上加了锁,保证了多线程安全,
缺点:频繁获取该单例对象时效率有所下降。
懒汉模式-双重校验锁(DCL,即 double-checked locking)-多线程安全
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan() {
}
public static LazyMan getInstance() {
//第一个if提高效率,多个线程只有在lazyMan为null时才会等待锁,如果已经创建过单例对象则直接返回
if (lazyMan == null) {
synchronized (LazyMan.class) {
//确保单例安全,lazyMan为null时,只有第一个获得锁的线程才会创建该单例对象,其余线程会直接返回
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
JDK1.5
是不是看起来没毛病?
然鹅, lazyMan = new LazyMan() 的操作并不是原子性的,需要经过:
- 为这个对象分配内存
- 执行构造方法,初始化对象
- 将对象指向内存空间
其中,步骤2和3可能会出现指令重排,
也就是执行了步骤1后,接着执行了步骤3,
假如其他线程此时到了第一个if (lazyMan == null)判断,会发现该对象的引用不为null,于是返回了该单例对象
但此时对象尚未完成初始化操作,
那么就拿到了一个不安全的单例对象
所以需要禁止指令重排,使用volatile修饰该对象即可保证线程安全
public class LazyMan {
private volatile static LazyMan lazyMan;
private LazyMan() {
}
public synchronized static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
}
静态内部类-多线程安全
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return InnerClass.instance;
}
public static class InnerClass{
private static final Singleton instance = new Singleton();
}
}
只有通过显式调用 getInstance 方法时,才会实例化 instance,可以实现懒加载
枚举-多线程安全且禁止反射
枚举在编译时会继承枚举类,并声明为final,天生防止反射,JDK1.5
public enum Singleton1{
SINGLETON;
public static Singleton1 getSingleton(){
return SINGLETON;
}
}
public class Test {
public static void main(String[] args) {
Singleton1 singleton1 = Singleton1.SINGLETON;
//Singleton1 singleton1 = Singleton1.getSingleton();//等价于上
Singleton1 singleton2 = Singleton1.SINGLETON;
//Singleton1 singleton2 = Singleton1.getSingleton();//等价于上
System.out.println("singleton1 = " + singleton1);
System.out.println("singleton2 = " + singleton2);
System.out.println(singleton1 == singleton2);//true
}
}
ctrl+鼠标左键
若使用枚举的构造实例化对象则抛出异常
强行使用反射破环枚举单例
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton1 singleton1 = Singleton1.SINGLETON;
Constructor<Singleton1> declaredConstructor = Singleton1.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
Singleton1 singleton2 = declaredConstructor.newInstance();
System.out.println(singleton1==singleton2);
}
}
结果会失败并抛出异常
反射在双重校验锁创建下多个对象
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan instance = LazyMan.getInstance();//创建单例对象
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//获取LazyMan的无参构造器
declaredConstructor.setAccessible(true);//设置构造器可操作
LazyMan LazyMan1 = declaredConstructor.newInstance();//通过私有构造器实例化对象
LazyMan LazyMan2 = declaredConstructor.newInstance();
System.out.println("单例instance = " + instance);
System.out.println("反射LazyMan1 = " + LazyMan1);
System.out.println("反射LazyMan2 = " + LazyMan2);
}
可以看到,三个对象完全不同,因为反射是通过操作私有构造器的方式创建出来的
双重校验锁禁止反射
public class LazyManBan {
private volatile static LazyManBan lazyMan;
private LazyManBan() {
if (lazyMan != null) {
throw new RuntimeException("请勿使用反射!");
}
}
public static synchronized LazyManBan getInstance() {
if (lazyMan == null) {
synchronized (LazyManBan.class) {
if (lazyMan == null) {
lazyMan = new LazyManBan();
}
}
}
return lazyMan;
}
}
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyManBan instance = LazyManBan.getInstance();//创建单例对象
Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyManBan LazyManBan1 = declaredConstructor.newInstance();
LazyManBan LazyManBan2 = declaredConstructor.newInstance();
System.out.println("单例instance = " + instance);
System.out.println("LazyManBan1 = " + LazyManBan1);
System.out.println("LazyManBan2 = " + LazyManBan2);
}
}
反射无法使用,你以为结束了吗?
反射无法使用是因为使用了getInstance()方法将单例对象lazyMan实例化
假如不赋值呢?也就是直接使用反射获取对象
双重校验锁不实例化对象直接反射
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyManBan LazyManBan1 = declaredConstructor.newInstance();
LazyManBan LazyManBan2 = declaredConstructor.newInstance();
LazyManBan LazyManBan3 = declaredConstructor.newInstance();
System.out.println("LazyManBan1 = " + LazyManBan1);
System.out.println("LazyManBan2 = " + LazyManBan2);
System.out.println("LazyManBan3 = " + LazyManBan3);
}
}
对象还是被创建出来了
原因就是因为防止反射判断的是静态的lazyMan
是一个已经定义好了的对象
然而反射每次都是通过构造函数创建的对象,并没有将静态的lazyMan实例化,也就是两者没什么关系
使用标志防止双重校验锁下多次反射
public class LazyManBanFlag {
private static boolean flag = false;
private volatile static LazyManBanFlag lazyMan;
private LazyManBanFlag() {
if (!flag) {
flag=true;
}else {
throw new RuntimeException("请勿使用反射!");
}
}
public static synchronized LazyManBanFlag getInstance() {
if (lazyMan == null) {
synchronized (LazyManBanFlag.class) {
if (lazyMan == null) {
lazyMan = new LazyManBanFlag();
}
}
}
return lazyMan;
}
}
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyManBan instance = LazyManBan.getInstance();//创建单例对象
Constructor<LazyManBanFlag> declaredConstructor = LazyManBanFlag.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyManBanFlag LazyManBanFlag1 = declaredConstructor.newInstance();
LazyManBanFlag LazyManBanFlag2 = declaredConstructor.newInstance();
System.out.println("单例instance = " + instance);
System.out.println("LazyManBanFlag1 = " + LazyManBanFlag1);
System.out.println("LazyManBanFlag2 = " + LazyManBanFlag2);
}
}
此时无论是正常获取对象+反射
还是直接多次反射
都会抛出异常
套娃永无止境
你以为这就安全了吗?
反射也可以直接操作变量,private?
每次反射创建一个对象,设置标志为 false
…