java的单例模式
什么是单例模式(Singleton) 设计模式的一种 一个类只有一个实例对象
单例模式的特点
1、只能有一个实例。
2、必须自己创建自己的唯一实例。
3、必须给所有其他对象提供这一实例。
为什么要使用单例模式
有些类频繁创建和销毁消耗资源, 可以完全复用,比如IO处理,数据库操作,全局用一个类就好了.
所以我们将这个类设置成单例类,全局只有一个,一次创建就可重复使用.
单例模式的写法
1.饿汉式 静态常量
2.饿汉式 静态代码块
3.懒汉式
4.懒汉式 + synchronized
5.懒汉式 + 双重检锁
6.懒汉式 + 双重检锁 + volatile
7.静态内部类
8.枚举
写法1.饿汉式 静态常量
public class HungryMan { private HungryMan(){} private static HungryMan hungryMan = new HungryMan(); public static HungryMan getInstance(){ return hungryMan; } }
写法2.饿汉式 静态代码块
public class HungryMan { private static HungryMan hungryMan; static { hungryMan = new HungryMan(); } private HungryMan(){} public static HungryMan getInstance() { return hungryMan; } }
说明下
懒汉式:指全局的单例实例在第一次被使用时构建。
饿汉式:指全局的单例实例在类装载时构建。
从它们的区别也能看出来,日常我们使用的较多的应该是懒汉式的单例,毕竟按需加载才能做到资源的最大化利用
写法3.懒汉式
public class LazyMan { private LazyMan(){} private static LazyMan lazyMan; public static LazyMan getInstance(){ if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } }
说明下
但是懒汉式是线程不安全的,多个线程访问会出现问题
/** * @description: 懒汉式 单线程可用 */ public class LazyMan { private LazyMan(){ System.out.println("运行线程 = " + Thread.currentThread().getName()); } private static LazyMan lazyMan; public static LazyMan getInstance(){ if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } } /*** * 模拟多线程访问单例类 懒汉式线程不安全 因为多线程情况下 会构建多个对象 */ class Test{ public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
运行结果如下
说明下 解决写法3 问题 使用写法4
4.懒汉式 + synchronized
/** * @description:懒汉式 + synchronized */ public class LazyMan { private LazyMan() { System.out.println("运行线程 = " + Thread.currentThread().getName()); } private static LazyMan lazyMan; public static synchronized LazyMan getInstance() { if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } } class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
运行结果如下
说明下 写法4 性能低 不推荐使用 解决使用写法5
写法5.懒汉式 + 双重检锁
/** * @description: 懒汉式 + 双重检锁 */ public class LazyMan { private LazyMan() { System.out.println("运行线程 = " + Thread.currentThread().getName()); } private static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
运行结果如下
说明下
new LazyMan(); 不是原子性操作
需要完成以下几步 1 2 3
1.分配空间
2.执行构造方法,初始化构造
3.对象指向内存地址
JVM虚拟机为了执行效率可能对以上操作进行指令重排 原来的123可能编程132
多线程情况下 还是不安全的 解决使用写法6
写法6.懒汉式 + 双重检锁 + volatile
/** * @description: 懒汉式 + 双重检锁 + volatile关键字 修饰 避免指令重排 */ public class LazyMan { private LazyMan() { System.out.println("运行线程 = " + Thread.currentThread().getName()); } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
运行结果如下
写法7.使用静态内部类
静态内部类实现单例 /** * @description: 静态内部类 */ public class Holder { private Holder (){} public static Holder getInstance(){ return innerClass.holder; } public static class innerClass{ private static final Holder holder = new Holder(); } }
说明下
还有一个问题就是反射可以破坏单例
情况一 一个使用getInstance获得单例对象 一个通过反射获得对象
/** * @description: 懒汉式 + 双重检锁 + volatile关键字 修饰 */ public class LazyMan { private LazyMan() { System.out.println("运行线程 = " + Thread.currentThread().getName()); } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class TestReflect { /*** * 使用反射会破坏单例 * @param args */ public static void main(String[] args) throws Exception { LazyMan instance = LazyMan.getInstance(); //使用反射来获取单例对象 Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan instance2 = constructor.newInstance(); System.out.println(instance); System.out.println(instance2); } }
运行结果如下
说明下
通过反射破坏了单例 创建了两个对象
解决加锁 手动抛出异常
public class LazyMan { private LazyMan() { synchronized (LazyMan.class){ if (lazyMan != null) { throw new RuntimeException("禁止使用反射破坏单例"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class TestReflect { /*** * 使用反射会破坏单例 * @param args */ public static void main(String[] args) throws Exception { LazyMan instance = LazyMan.getInstance(); //使用反射来获取单例对象 Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan instance2 = constructor.newInstance(); System.out.println(instance); System.out.println(instance2); } }
运行结果如下
情况二 直接通过反射创建两个单例对象
/** * @description: 懒汉式 + 双重检锁 + volatile关键字 修饰 + synchronized */ public class LazyMan { private LazyMan() { synchronized (LazyMan.class){ if (lazyMan != null) { throw new RuntimeException("禁止使用反射破坏单例"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class TestReflect { /*** * 使用反射会破坏单例 * @param args */ public static void main(String[] args) throws Exception { //直接通过反射创建两个单例对象 Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan instance2 = constructor.newInstance(); LazyMan instance3= constructor.newInstance(); System.out.println(instance2); System.out.println(instance3); } }
运行结果如下
说明下
还是出现了两个对象破坏了单例
解决设置标志位
/** * @description: 懒汉式 + 双重检锁 + volatile关键字 修饰 + synchronized +标志位 */ public class LazyMan { //设置标志位 private static boolean flag = false; private LazyMan() { synchronized (LazyMan.class){ if (flag == false){ flag = rue; }else { throw new RuntimeException("禁止使用反射破坏单例"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class TestReflect { /*** * 使用反射会破坏单例 * @param args */ public static void main(String[] args) throws Exception { //直接通过反射创建两个单例对象 Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan instance2 = constructor.newInstance(); LazyMan instance3= constructor.newInstance(); System.out.println(instance2); System.out.println(instance3); } }
运行结果如下
情况三 直接通过反射创建两个单例对象 破坏标志位
/** * * @description: 懒汉式 + 双重检锁 + volatile关键字 修饰 */ public class LazyMan { //设置标志位 private static boolean flag = false; private LazyMan() { synchronized (LazyMan.class){ if (flag == false){ flag = true; }else { throw new RuntimeException("禁止使用反射破坏单例"); } } } private volatile static LazyMan lazyMan; public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } return lazyMan; } } class TestReflect { /*** * 使用反射会破坏单例 破坏标志位 * @param args */ public static void main(String[] args) throws Exception { //破坏标志位 Field flag = LazyMan.class.getDeclaredField("flag"); flag.setAccessible(true); //直接通过反射创建两个单例对象 Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan instance2 = constructor.newInstance(); flag.set(instance2,false); LazyMan instance3= constructor.newInstance(); System.out.println(instance2); System.out.println(instance3); } }
运行结果如下
说明下
通过反射 破坏标志位 仍然能破坏单例
分析
在Constructor.getInstance 方法中 的这段代码说明反射不能破坏枚举的 引出写法8解决
写法8.枚举
/** * 使用枚举 */ public enum EnumSingle { INSTANCE; public EnumSingle getInstance() { return INSTANCE; } } class Test{ public static void main(String[] args) { EnumSingle instance = EnumSingle.INSTANCE; EnumSingle instance2 = EnumSingle.INSTANCE; System.out.println(instance); System.out.println(instance2); } }
运行结果如下
//验证反射不能破坏枚举
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingle enumSingle = constructor.newInstance();
EnumSingle enumSingle2 = constructor.newInstance();
运行结果如下 和在Constructor.getInstance 方法中 抛出的异常一样
说明下
所有枚举都继承Enum
它的构造方法有两个参数