单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,单例模式是创建型模式。
单例模式分为饿汉式单例和懒汉式单例
public class Hungry {
private final static Hungry hungry = new Hungry();
private Hungry() {
// 构造器私有
}
public static Hungry getInstance(){
return hungry;
}
}
public class Lazy {
private static Lazy single;
private Lazy() {
// 构造器私有
}
public static Lazy getInstance(){
if (single==null){
single = new Lazy();
}
return single;
}
}
public class Lazy {
private static Lazy single;
private Lazy() {
// 构造器私有
System.out.println(Thread.currentThread().getName()+" ok");
}
public static Lazy getInstance(){
if (single==null){
single = new Lazy();
}
return single;
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
测试:
此时还是创建了5个对象,说明懒汉式单例在多线程下不安全
public class Lazy {
private static Lazy single;
private Lazy() {
// 构造器私有
}
// 双重检测锁模式的懒汉式单例——DCL懒汉式
public static Lazy getInstance(){
if (single==null){
synchronized (Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
}
public class Lazy {
private static volatile Lazy single; // 加上volatile
private Lazy() {
// 构造器私有
}
public static Lazy getInstance(){
if (single==null){
synchronized(Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
}
getInstance方法中 single = new Lazy() 不是一个原子性操作,创建对象的过程是:
① 在堆内存中开辟内存空间
② 执行构造方法,初始化对象
③ 把这个对象指向内存空间
极端情况,JVM中可能发生指令重排,执行过程变为 1 3 2 ,所以必须在 single 前面加上 volatile,volatile 能保证线程间的可见性,防止指令重排
public class Holder {
private Holder(){
}
public static class InnerClass{
private static final Holder single = new Holder();
}
public static Holder getInstance(){
return InnerClass.single;
}
}
public class Lazy {
private static volatile Lazy single; // 加上volatile
private Lazy() {
// 构造器私有
}
public static Lazy getInstance(){
if (single==null){
synchronized(Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
// 反射暴力破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy single1 = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
}
private Lazy() {
// 构造器私有
synchronized (Lazy.class){
if (single!=null){
throw new RuntimeException("不要使用反射来破坏到单例");
}
}
}
但此时若又修改测试方法为:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single1 = declaredConstructor.newInstance();
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
测试:
单例又再次被破坏,但我们可以再增加一个标志位flag,然后修改构造方法,其他不变:
public class Lazy {
private static volatile Lazy single; // 加上volatile
private static boolean flag = false;
private Lazy() {
// 构造器私有
synchronized (Lazy.class){
if (flag==false){
flag = true;
}else {
throw new RuntimeException("不要使用反射来破坏到单例");
}
}
}
public static Lazy getInstance(){
if (single==null){
synchronized(Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
// 反射暴力破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single1 = declaredConstructor.newInstance();
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
}
但我们也可以通过反射来得到flag并修改它的值,从而破坏单例,修改测试方法为:
public static void main(String[] args) throws Exception {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single1 = declaredConstructor.newInstance();
Field flag = Lazy.class.getDeclaredField("flag"); // 通过反射获得flag属性
flag.set(single1,false); // 把flag的值改为false
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
测试:
这说明在反射下,上面的这些单例模式都是不安全的!所以我们可以使用枚举实现单例!
public enum EnumSingle {
SINGLE;
public EnumSingle getInstance(){
return SINGLE;
}
}
反射的newInstance()方法写死了不能破坏枚举
参考:
https://blog.csdn.net/as513385/article/details/110082439.
https://blog.csdn.net/mnb65482/article/details/80458571?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.baidujs&dist_request_id=1619618168424_01105&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.baidujs.