单例模式的定义是确保某个类在任何情况下都只有一个实例,并且需要提供一个全局的访问点供调用者访问该实例的一种模式。
实现以上模式基于以下必须遵守的两点:
1.构造方法私有化
2.提供一个public static修饰的方法,来返回实例
单例模式进化史:
起初是饥汉模式
package com.demo;
/**
* 饥汉模式
* 缺点:无法延迟加载
* @author huang
*
*/
public class Demo01 {
private Demo01() {}
private static Demo01 test01 = new Demo01();
public static Demo01 getInstance() {
return test01;
}
}
为了提高效率,需要延迟加载,就出现了懒汉模式
package com.demo;
/**
* 懒汉模式
* 缺点:线程不安全
* @author huang
*
*/
public class Demo02 {
private Demo02() {}
private static Demo02 test01 = null;
public static Demo02 getInstance() {
return (null == test01) ? new Demo02() : test01;
}
}
效率可以了,线程又不安全了,继续改
package com.demo;
/**
* 缺点:高并发情况下性能差
* @author huang
*
*/
public class Demo03 {
private Demo03() {}
private static Demo03 test01 = null;
public synchronized static Demo03 getInstance() {
return (null == test01) ? new Demo03() : test01;
}
}
synchronized关键字修饰的是方法,效率又下去了
package com.demo;
/**
* 饥汉模式
* 缺点:JVM重排序的机制,可能会返回没有初始化的对象
* @author huang
*
*/
public class Demo04 {
private Demo04() {}
private static Demo04 test01 = null;
public static Demo04 getInstance() {
if(null == test01) {
synchronized (Demo04.class) {
if(null == test01) {
test01 = new Demo04();
}
}
}
return test01;
}
}
改用synchronized关键字修饰代码块,而且还进行了双次校验,可惜呀,有可能发生重排序,导致返回的是一个没有初始化的实例对象
继续改进:使用volatile关键字可以禁止重排序
package com.demo;
/**
* 饥汉模式
* 优点:线程安全
* 缺点:通过反射获取的对象与调用getInstance()返回的对象不是同一个
* @author huang
*
*/
public class Demo05 {
private Demo05() {}
private static volatile Demo05 test01 = null;
public static Demo05 getInstance() {
if(null == test01) {
synchronized (Demo05.class) {
if(null == test01) {
test01 = new Demo05();
}
}
}
return test01;
}
}
另外:Java虚拟机在进行类的加载过程中,会执行类的初始化。在执行初始化期间,Java虚拟机可以同步多个线程对一个类的初始化,保证类的初始化的线程安全性。所以我们也可以这么玩
package com.demo;
/**
* 饥汉模式
* 优点:线程安全
* 缺点:通过反射获取的对象与调用getInstance()返回的对象不是同一个
* @author huang
*
*/
public class Demo06 {
private Demo06() {}
public synchronized static Demo06 getInstance() {
return InnerClass06.test01;
}
private static class InnerClass06 {
private static Demo06 test01 = new Demo06();
}
}
然后,还是还是有漏洞
package com.test;
import java.lang.reflect.Constructor;
import com.demo.Demo06;
public class Ctest06 {
public static void main(String[] args) throws Exception {
Demo06 o1 = Demo06.getInstance();
Demo06 o2 = Demo06.getInstance();
System.out.println("o1==o2:"+(o1 == o2));// true
// 没抵挡住反射的诱惑啊
Constructor<Demo06> declaredConstructors = Demo06.class.getDeclaredConstructor();
declaredConstructors.setAccessible(true);
Demo06 o3 = declaredConstructors.newInstance();
System.out.println("o1==o3:"+(o1 == o3));// false
}
}
执行以上代码,我们通过反射可以得到不同的实例,这就违背了我们单例模式的初衷。
最后,我们只能利用枚举了,枚举类无法被反射
package com.bean;
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.demo;
import com.bean.User;
/**
* 饥汉模式
* 优点:线程安全 Enum无法通过反射来获取实例
* @author huang
*
*/
public enum Demo07 {
SINGLETON;
private User user;
private Demo07() {
user = new User();
}
public User getInstance() {
return user;
}
}
package com.test;
import com.bean.User;
import com.demo.Demo07;
public class Ctest07 {
public static void main(String[] args) {
User demo1 = Demo07.SINGLETON.getInstance();
User demo2 = Demo07.SINGLETON.getInstance();
System.out.println("demo1==demo2:"+(demo1 == demo2));// true
}
}
推荐使用枚举来实现单例模式