核心作用:
常见场景:
// 饿汉式单例类
class Hungry {
// 一上来就实例化,可能会浪费空间
private final static Hungry HUNGRY = new Hungry();
// 私有化构造器
private Hungry(){}
// 定义一个方法返回单例对象
public Hungry getInstance() {
return HUNGRY;
}
}
// 懒汉式单例
class LazyMan {
// 在第一次使用才复制(节省空间)
private static LazyMan lazyMan;
//私有化构造器
private LazyMan() {}
// 调用方法获取单例对象
public static LazyMan getInstance() {
// 当第一次访问为 null 的时候才初始化
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
使用测试:
public static void main(String[] args) {
LazyMan lazyMan1 = LazyMan.getInstance();
LazyMan lazyMan2 = LazyMan.getInstance();
System.out.println(lazyMan1 == lazyMan2);
}
输出结果为true:说明单例创建完成!
测试多线程情况下;
修改构造方法
//私有化构造器
private LazyMan() {
System.out.println("LazyMan 构造方法调用");
}
编写测试代码:
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
查看输出:
发现在多线程情况下构造方法被调用了多次,此时就破坏了单例模式!
解决方案第一步:添加创建时的锁-让getInstance()方法同时只能被一个线程调用
// 调用方法获取单例对象
public synchronized static LazyMan getInstance() {
// 当第一次访问为 null 的时候才初始化
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
查看输出:
成功解决了问题:
但是这种锁每次进入方法都会进行检测,很浪费效率,我们将代码修改为:双重检锁模式
// 调用方法获取单例对象
public static LazyMan getInstance() {
// 访问的时候为 null 才进入下面代码(当不是第一次访问的时候,提高运行效率)
if (lazyMan == null) {
synchronized (LazyMan.class) {
// 当第一次访问为 null 的时候才初始化
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
分析代码:
它不是一个原子性操作:
1、分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间 执行顺序123,132都有可能 A:123 B:132 B把这个对象指向这个空间,发现不为空执行return 但是此时在线程A中,lazyMan还没有完成构造,lazyMan要加volatile,防止指令重排
private volatile static LazyMan lazyMan;
静态内部类方式创建单例:
class Holder {
private Holder() {}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
private static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
以上都不安全,可以通过反射破坏!
测试:
public static void main(String[] args) throws Exception{
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
通过测试发现通过反射的方式,还是会破坏单例模式:
解决方案:(对构造方法在进行判断并加锁)
//私有化构造器
private LazyMan() {
synchronized(Holder.class){
if (lazyMan != null){
throw new RuntimeException("当前单例对象已经创建,请不要再次尝试调用构造方法!");
}
}
}
运行测试:
但是仍然可以通过如下方式破坏:
public static void main(String[] args) throws Exception{
// LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
还是有bug
再次寻找解决方案:(使用一个标志位来进行加密)
private static boolean biaozhiwei = false;
//私有化构造器
private LazyMan() {
synchronized(Holder.class){
if(biaozhiwei == false){
biaozhiwei = true;
}else {
throw new RuntimeException("当前单例对象已经创建,请不要再次尝试调用构造方法!");
}
}
}
运行测试:
成功解决上面的问题:
但是在这边情况下,还是有可能被破译,要是在知道标志位名称的情况下(通过反编译进行查看属性名称,在通过反射将值进行修改)还是有漏洞:
public static void main(String[] args) throws Exception{
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
// 将标志位的值在该会为 false
Field biaozhiwei = LazyMan.class.getDeclaredField("biaozhiwei");
biaozhiwei.setAccessible(true);
biaozhiwei.set(instance1, false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
再次成功破坏单例!
查看jdk自带的枚举怎么实现单例的。
使用枚举,我们就可以防止反射破坏了。
// enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
//java.lang.NoSuchMethodException: com.zzy.single.EnumSingle.<init>() 没有空参构造方法
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
反编译
使用jad工具反编译为java
枚举类型的最终反编译源码:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
public final class EnumSingle extends Enum{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/kuang/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
源码骗了我们,用了一个有参构造器
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
//java.lang.IllegalArgumentException: Cannot reflectively create enum objects
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
抛出异常: java.lang.IllegalArgumentException: Cannot reflectively create enum objects
。
评论
登录后才可以进行评论哦!