一、饿汉式单例模式:
饿汉式:饥饿的人,看到食物就上来抢。对应在代码中,在类加载时就立刻实例化。
public class HungrySingleton {
//假如类中存在这样的空间开辟的操作:
//使用饿汉式时,不管用不用,上来就给你开了再说,造成空间浪费。
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//1、私有构造器
private HungrySingleton(){
}
//2、类的内部创建对象
private final static HungrySingleton HUNGRYSINGLE = new HungrySingleton();
//3、向外暴露一个静态的公共方法。 getInstance
public static HungrySingleton getInstance(){
return HUNGRYSINGLE;
}
public static void main(String[] args) {
//单线程:
HungrySingleton instance1 = HungrySingleton.getInstance();
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println(instance1 == instance2);//true
System.out.println("------------");
//多线程:
new Thread(()->{
HungrySingleton instance_A = HungrySingleton.getInstance();
System.out.println(instance_A);
//com.kuangstudy.Singleton.HungrySingleton@626213bf
}).start();
new Thread(()->{
HungrySingleton instance_B = HungrySingleton.getInstance();
System.out.println(instance_B);
//com.kuangstudy.Singleton.HungrySingleton@626213bf
}).start();
}
}
优点:
- 基于 classloader 机制避免了多线程的同步问题,在类装载的时候就完成实例化。避免了线程同步问题==>线程安全。
- 没有加锁,执行效率会提高。
- 简单好用。
缺点:
- 在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
二、懒汉式单例模式:
2.1 非线程安全:
public class LazySingleton {
//1.私有化构造函数
private LazySingleton() {
System.out.println(Thread.currentThread().getName()+" ->OK");
}
//2.创建对象(容器)
private static LazySingleton lazyMan ;
//3.对外提供静态实例化方法,判断在对象为空的时候创建
public static LazySingleton getInstance(){
//用的时候再加载
if (lazyMan == null) {
lazyMan = new LazySingleton();
}
return lazyMan;
}
//测试单线程下懒汉式单例
public static void main(String[] args) {
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
//实例化两个只出现:main ->OK
System.out.println(instance1);//com.kuangstudy.Singleton.LazySingleton@61bbe9ba
System.out.println(instance2);//com.kuangstudy.Singleton.LazySingleton@61bbe9ba
}
}
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
public class LazySingleton {
//1.私有化构造函数
private LazySingleton() {
System.out.println(Thread.currentThread().getName()+" ->OK");
}
//2.创建对象(容器)
private static LazySingleton lazyMan ;
//3.对外提供静态实例化方法,判断在对象为空的时候创建
public static LazySingleton getInstance(){
//用的时候再加载
if (lazyMan == null) {
lazyMan = new LazySingleton();
}
return lazyMan;
}
//开启多条线程实例化LazySingleton
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
// new Thread(()->{LazySingleton.getInstance();}).start();
new Thread(LazySingleton::getInstance).start();
}
}
//结果实例化超过1个对象:(不唯一)
/*
Thread-0 ->OK
Thread-3 ->OK
Thread-2 ->OK
Thread-1 ->OK
*/
}
2.2 线程安全:
public class LazySingletonWithDCL {
//1.私有化构造函数
private LazySingletonWithDCL() {
System.out.println(Thread.currentThread().getName() + " ->OK");
}
//2.创建对象(容器)
// private static LazySingletonWithDCL lazyMan ;
//5.new 不是一个原子性操作:
private volatile static LazySingletonWithDCL lazyMan;
//3.对外提供静态实例化方法
//4.为保证线程安全,需要上锁
public static LazySingletonWithDCL getInstance() {
if (lazyMan == null) {
synchronized (LazySingletonWithDCL.class) {
if (lazyMan == null) {
lazyMan = new LazySingletonWithDCL();
}
}
}
return lazyMan;
}
//开启多条线程实例化LazySingletonWithDCL
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
// new Thread(()->{LazySingletonWithDCL.getInstance();}).start();
new Thread(LazySingletonWithDCL::getInstance).start();
}
}
}
双检锁/双重校验锁(DCL,即 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。
这样,实例化代码只用执行一次,后面再次访问时,判断 if(lazyMan == null),直接return实例化对象,也避免的反复进行方法同步.
线程安全;延迟加载;效率较高
2.3 问题:
为什么要synchronized (LazySingletonWithDCL.class)而不是对方法加锁?
简单回答:
synchronized 重量级锁,锁的范围越小越好,class只有一个,而方法会每次都执行,因此为了提高效率,锁对象而不是锁方法。
new对象的过程为什么不是一个原子性操作?
实锤 new 对象不是原子性操作,会执行以下操作:
分配内存空间
执行构造方法,初始化对象
把对象指向空间
实践出真知:
1、这是原子类AtomicInteger.getAndIncrement()方法,反编译后:
2、手动new一个对象
public class TestNew {
private static int num = 0;
public static void main(String[] args) {
TestNew testNew = new TestNew();
}
public void add(){
num++;
};
}
反编译后:
三、反射破坏单例:
3.1 反射破坏:对DCL饿汉式单例进行破解
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazySingletonWithDCL instance = LazySingletonWithDCL.getInstance();
//利用反射创建对象
//获取无参构造
Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
//开放权限
declaredConstructor.setAccessible(true);
LazySingletonWithDCL instance_withReflect = declaredConstructor.newInstance();
System.out.println(instance); //LazySingletonWithDCL@61bbe9ba
System.out.println(instance_withReflect); //LazySingletonWithDCL@610455d6
System.out.println(instance == instance_withReflect); //false
}
3.2 懒汉升级:增加校验反击
因为反射走的是无参构造,可以在构造函数中进行判断
public class LazySingletonWithDCL {
//1.私有化构造函数
//6.增加对反射的判断
private LazySingletonWithDCL() {
synchronized (LazySingletonWithDCL.class){
//6.1如果此时已经有实例,阻止反射创建
if (lazyMan != null){
throw new RuntimeException("不要试图通过反射破解单例");
}
}
System.out.println(Thread.currentThread().getName() + " ->OK");
}
//2.创建对象(容器)
// private static LazySingletonWithDCL lazyMan ;
//5.new 不是一个原子性操作:
private volatile static LazySingletonWithDCL lazyMan;
//3.对外提供静态实例化方法
//4.为保证线程安全,需要上锁
public static LazySingletonWithDCL getInstance() {
if (lazyMan == null) {
synchronized (LazySingletonWithDCL.class) {
if (lazyMan == null) {
lazyMan = new LazySingletonWithDCL();
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
LazySingletonWithDCL instance = LazySingletonWithDCL.getInstance();
System.out.println(instance); //创建成功: LazySingletonWithDCL@61bbe9ba
//利用反射创建对象
//获取无参构造
Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
//开放权限
declaredConstructor.setAccessible(true);
LazySingletonWithDCL instance_withReflect = declaredConstructor.newInstance();
System.out.println(instance_withReflect); //LazySingletonWithDCL@610455d6
System.out.println(instance == instance_withReflect); //false
}
}
main ->OK
com.kuangstudy.Singleton.LazySingletonWithDCL@61bbe9ba
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.kuangstudy.Singleton.LazySingletonWithDCL.main(LazySingletonWithDCL.java:60)
Caused by: java.lang.RuntimeException: 不要试图通过反射破解单例
at com.kuangstudy.Singleton.LazySingletonWithDCL.(LazySingletonWithDCL.java:20)
… 5 more
只创建出了一个实例,并成功拦截了 通过反射创建对象的行为
3.3 反射加大破坏:两个对象都是用反射创建
public static void main(String[] args) throws Exception {
//获取类模板class
Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
//开放权限
declaredConstructor.setAccessible(true);
//通过空参创建实例
LazySingletonWithDCL instance_withReflect1 = declaredConstructor.newInstance();
LazySingletonWithDCL instance_withReflect2 = declaredConstructor.newInstance();
System.out.println(instance_withReflect1);//LazySingletonWithDCL@61bbe9ba
System.out.println(instance_withReflect2);//LazySingletonWithDCL@610455d6
System.out.println(instance_withReflect1 == instance_withReflect2);//false
}
结果非常明显:再一次破坏了单例模式
3.4 懒汉再度防守:加入标志位
在空参构造方法里运用标志位,因为实例化对象需要获取类模板:
- 当获取了一次类模板之后,就把标志位flag置反。
- 等一次通过反射获取类模板创建对象的时候便能抛异常
public class LazySingletonWithDCL {
//7.加入标志位,防止多个反射破坏单例
private static boolean flag = true;
//1.私有化构造函数
//6.增加对反射的判断
private LazySingletonWithDCL() {
synchronized (LazySingletonWithDCL.class){
if (flag){
flag = false;
}else {
// //6.1如果此时已经有实例,阻止反射创建
// if (lazyMan != null){
throw new RuntimeException("不要试图通过反射破解单例");
// }
}
}
System.out.println(Thread.currentThread().getName() + " ->OK");
}
//2.创建对象(容器)
// private static LazySingletonWithDCL lazyMan ;
//5.new 不是一个原子性操作:
private volatile static LazySingletonWithDCL lazyMan;
//3.对外提供静态实例化方法
//4.为保证线程安全,需要上锁
public static LazySingletonWithDCL getInstance() {
if (lazyMan == null) {
synchronized (LazySingletonWithDCL.class) {
if (lazyMan == null) {
lazyMan = new LazySingletonWithDCL();
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
//获取类模板class
Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
//开放权限
declaredConstructor.setAccessible(true);
//通过空参创建实例
LazySingletonWithDCL instance_withReflect1 = declaredConstructor.newInstance();
System.out.println(instance_withReflect1);//LazySingletonWithDCL@61bbe9ba
LazySingletonWithDCL instance_withReflect2 = declaredConstructor.newInstance();
System.out.println(instance_withReflect2);
//报错:java.lang.RuntimeException: 不要试图通过反射破解单例
System.out.println(instance_withReflect1 == instance_withReflect2);
}
}
结果:
main ->OK
com.atguigu.test.LazySingletonWithDCL@27ddd392
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.atguigu.test.LazySingletonWithDCL.main(LazySingletonWithDCL.java:54)
Caused by: java.lang.RuntimeException: 不要试图通过反射破解单例
at com.atguigu.test.LazySingletonWithDCL.(LazySingletonWithDCL.java:19)
… 5 more
由运行结果可见,第一次反射创建成功,第二次反射由于标志位的原因报错了,保住了单例模式。至此,用过反射调用空参实例化的方法被标志位掐断
3.5 反射继续破坏:破坏标志位
public static void main(String[] args) throws Exception{
//获取标志位
Field flag = LazySingletonWithDCL.class.getDeclaredField("flag");
flag.setAccessible(true);
//获取类模板class
Constructor<LazySingletonWithDCL> declaredConstructor = LazySingletonWithDCL.class.getDeclaredConstructor(null);
//开放权限
declaredConstructor.setAccessible(true);
//通过空参创建实例
LazySingletonWithDCL instance_withReflect1 = declaredConstructor.newInstance();
System.out.println(instance_withReflect1);//LazySingletonWithDCL@61bbe9ba
//在获取并使用完类模板后,重新设置flag值:
flag.set(instance_withReflect1,true);
LazySingletonWithDCL instance_withReflect2 = declaredConstructor.newInstance();
System.out.println(instance_withReflect2);
}
结果:
main ->OK
com.kuangstudy.Singleton.LazySingletonWithDCL@61bbe9ba
main ->OK
com.kuangstudy.Singleton.LazySingletonWithDCL@511d50c0
3.6 该如何真正确保单例
解铃还须系铃人,查看反射中newInstance()方法
枚举可以不被反射破坏,构造枚举尝试单例模式:
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1.hashCode());//1639705018
System.out.println(instance2.hashCode());//1639705018
}
}
3.7 尝试用反射破坏枚举的单例模式
public static void main(String[] args) throws Exception {
//instance1正常获取
EnumSingleton instance1 = EnumSingleton.INSTANCE;
System.out.println(instance1); //INSTANCE
//instance2通过反射获取:
//1.获取其空参
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);
//2.打开权限:
constructor.setAccessible(true);
//3.实例化对象:
EnumSingleton instance2 = constructor.newInstance();
System.out.println(instance2);
}
结果:
instance1 正常实例化
但是在通过反射的时候报了错:
找不到EnumSingleton 的空参构造?
这就奇怪了。
idea查看class文件也是只有空参构造:
用javap -p -c .\EnumSingleton.class口令也是看到空参:
探究枚举类的构造函数:
上述方法行不通后得运用更加专业的工具进行反编译:
使用jad工具
使用命令:
.\jad.exe -sjava .\EnumSingleton.class
将class成功反编译为Java文件:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package com.kuangstudy.Singleton;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/kuangstudy/Singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
此时我们发现枚举类内部其实是一个有参的构造函数。
此时我们修改代码:
//1.获取其有参构造:
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
这时候我们就能如愿得到想要的报错了 (?怎么听起来怪怪的)
INSTANCE
Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.kuangstudy.Singleton.Test.main(EnumSingleton.java:40)