什么是单例模式(Singleton Pattern)
单例模式(Singleton Pattern)是比较简单容易理解的设计模式之一,这种设计模式是属于创建型模式,这种设计模式涉及到一个单一的类型,该类负责自己创建自己的对象,同时确保只有一个对象被创建,这个类需要对外提供一个访问获取对象的方法,可以直接访问,不需要实例化该类的对象
注意:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给给所有其他对象提供这一实例
- 构造方法要私有化,不能提供外界访问
单例模式实现的方式有多,本文就简单介绍在保证线程安全情况下常用的3种方式
单例应用场景
- Windows系统的任务管理器。
- Windows系统的回收站。
- 操作系统的文件系统,一个操作系统只能有一个文件系统。
- 数据库连接池的设计与实现。
- 多线程的线程池设计与实现。
- Spring中创建的Bean实例默认都是单例。
- Java-Web中,一个Servlet类只有一个实例。 等等.
单例模式的实现
饿汉式
描述:这种方式比较常用,但容易生成垃圾对象
优点:没有加锁,执行效率会提高
缺点:类加载时候就初始化了,浪费内存,没有懒加载 lazy loading 的效果
上代码:
public class Hungry {
//浪费资源
private Byte[] byte1 = new Byte[1024 * 1024];
private Byte[] byte2 = new Byte[1024 * 1024];
private Byte[] byte3 = new Byte[1024 * 1024];
private Byte[] byte4 = new Byte[1024 * 1024];
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
懒汉式
描述:这里已经是线程安全的单例了, 也可以加入自己的密钥相关,去判断是不是单例 , 但是这种也可以用反射破坏. 所以最后面给大家介绍一下枚举!
懒汉式这种方式具备很好的 lazy loading,能过在多线程种很好的工作,但是,效率很低,99%情况下不需要通过
优点:第一次调用才初始化,避免了内存浪费,具有懒加载的效果
缺点:必须加锁 synchronized 才能过保证单例,但是加锁会影响效率 , 反射可以破坏单例结构,可以看测试代码,大家可以跑一下
注意: 这里的new LazeMan() 创建对象的时候不是原子性的.
为什么要加volatile关键字?
1.分配内存空间
2.构造方法,初始化对象
3.指向内存地址
*
* cpu执行可能123或者132
* 有可能下个线程进来的时候,会直接判断 lazeMan==null 这个时候
* 内存地址已经存在了,但是对象还未初始化完成,所以对lazeMan要加 volatile;
code:
public class LazeMan {
private LazeMan() {
System.out.println(Thread.currentThread().getName() + "ok!");
}
//volatile 是因为其本身包含“禁止指令重排序”的语义,
private static volatile LazeMan lazeMan;
public static LazeMan getInstance() {
//加锁操作
if (lazeMan == null) {
synchronized (LazeMan.class) {
if (lazeMan == null) {
/**
* new LazeMan()过程不是一个原子操作
* 1.分配内存空间
* 2.构造方法,初始化对象
* 3.指向内存地址
*
* cpu执行可能123或者132
* 有可能下个线程进来的时候,会直接判断 lazeMan==null 这个时候
* 内存地址已经存在了,但是对象还未初始化完成,所以对lazeMan要加 volatile;
*/
lazeMan = new LazeMan();
}
}
}
return lazeMan;
}
public static void main(String[] args) throws Exception {
//测试多线程
// for (int i = 0; i < 10000000; i++) {
// new Thread(()->{
// LazeMan.getInstance();
// }).start();
// }
//反射可以破环构造方法私有
LazeMan instance = getInstance();
System.out.println(instance);
Constructor<LazeMan> declaredConstructor = LazeMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazeMan lazeMan1 = declaredConstructor.newInstance();
System.out.println(lazeMan1);
}
}
枚举单例
重点: 提问: 枚举里面构造方式是不是私有的 ?
那怎么知道枚举里面是有参还是无参构造呢?打开编译的文件发现里面有一个无参构造,但是代码跑出来报错,说明IDEA骗了我们,然后用jad反编译工具把class文件反编译后发现,里面是一个有参构造,而且还带了两个参数
枚举源码:
以下是测试代码, 反射不可已破坏
public enum Holder {
INSTANCE;
public Holder getInstance() {
return INSTANCE;
}
}
class Test {
public static void main(String[] args) throws Exception {
Holder instance = Holder.INSTANCE;
System.out.println(instance);
/**
* 1.反射尝试破环单例, 这里枚举里面显示没有空参构造方法,
* Holder.class.getDeclaredConstructor(null);
*
* 2. 报错: 明明有空参构造方法啊,源码显示的也有,但是报错
* Exception in thread "main" java.lang.NoSuchMethodException: onecrm.micro.membership.provider.Holder.<init>()
* at java.lang.Class.getConstructor0(Class.java:3082)
* at java.lang.Class.getDeclaredConstructor(Class.java:2178)
* at onecrm.micro.membership.provider.Test.main(Holder.java:33)
*
* 3. 用jad反编译后发现里面是 (String.class, int.class) 参数,如果传null
* 报错: 不能反射式创建enum对象, 所以枚举是天然的单例
* Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
* at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
* at onecrm.micro.membership.provider.Test.main(Holder.java:27)
*/
Constructor<Holder> declaredConstructor = Holder.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Holder holder = declaredConstructor.newInstance();
System.out.println(holder);
}
}