设计模式-单例设计模式

1. 懒汉式

概念:使用的时候才初始化,jvm只有一个实例。

public class LazyMan {
    public LazyMan() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    public static LazyMan lazyMan;

    public static LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 300; i++) {
            new Thread(() -> getInstance()).start();
        }
    }
}

会出现:
在这里插入图片描述
可见并没有实现单例模式。

1.1实现方式1

如果在多线程下使用:必须加上synchronized才可以保证只有一个对象被创建

public class LazySingletonTest {
    public static void main(String[] args) {
        new Thread(()->{
            LazySingleton lazySingleton1 = LazySingleton.getInstance();
            System.out.println(lazySingleton1);
        }).start();

        new Thread(()->{
            LazySingleton lazySingleton2 = LazySingleton.getInstance();
            System.out.println(lazySingleton2);
        }).start();
    }
}

class LazySingleton{
    private static  LazySingleton lazySingleton = null;
    //构造器私有
    private LazySingleton(){}

    //获取实例
    public static synchronized LazySingleton getInstance(){
        if( lazySingleton == null ){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

优化LazySingleton类,如果我们将synchronized加在了静态方法上,那么锁住的就是整个类,我们可以这样优化:

    //获取实例(优化)
    public static  LazySingleton getInstance(){
        if( lazySingleton == null ){
            synchronized (LazySingleton.class){
                if( lazySingleton == null )
                lazySingleton = new LazySingleton();
            }
        }
        return lazySingleton;
    }

分析
在这里插入图片描述
如果线程1先进入同步代码块后,线程2在外面等候,当线程1完成对象创建后,此时lazySingleton对象已经不为空,就不会再创建lazySingleton对象了,实现了单例设计,优化了锁的范围,提高了效率。
优化引用变量
在我们进行多线程编程时,会出现new出来的变量为空,导致xxx线程的对象是空的,引发空指针异常,为什么会出现这个情况呢?出现这个问题很重要的原因是JVM虚拟机会对一些指令进行重排序,例如:

正常的顺序:
	//我们new 一个对象
	Demo demo = new Demo();
	//可以使用javap命令对class字节码文件进行反汇编,创建Demo对象具体指令
	0:new      #2                            1.分配空间---->返回一个指向该空间的内存引用
	3:dup									  2.堆栈的初始化 
	4:invokespecial	 #3				      3.对分配的空间进行初始化
	7:astore_1 						       4.把内存引用赋值给demo变量
重排序(就是顺序可能会乱掉):
	1.分配空间---->返回一个指向该空间的内存引用
    2.堆栈的初始化 	
    4.把内存引用赋值给demo变量
    3.对空间进行初始化

为了避免这一情况的出现,可以使用volatile关键字修饰引用变量

private static  volatile LazySingleton lazySingleton = null;

作用就是可以防止JVM指令重排。

使用注意点

  1. 需要保证线程安全(synchronized)
  2. 双重检查优化synchronized
  3. 防止指令重排

1.2 实现方式2

  • 使用静态内部类实现懒加载
  • 主要依据:静态内部类调用的时候才加载
class LazySingleton2{
    //构造器私有
    private LazySingleton2(){}
    //静态内部类
    private static  class InnerClass{
        private  static LazySingleton2 lazySingleton2 = new LazySingleton2();
    }
    //获取对象
    public static LazySingleton2 getInstance(){
        return InnerClass.lazySingleton2;
    }
}

但是,需要注意反射攻击,所谓反射攻击,就是在new完一个对象后,还可以通过反射来再创建一个对象,这在单例模式里是不被允许的。
演示反射攻击:

/**
 * 通过反射攻击来影响LazySingletonTest2类的单例模式
 */
public class ReflectAct {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazySingleton2> constructor = LazySingleton2.class.getDeclaredConstructor();
        constructor.setAccessible(true);  //设置访问权限
        LazySingleton2 lazySingleton2 = constructor.newInstance();
        LazySingleton2 instance = LazySingleton2.getInstance();
        System.out.println(lazySingleton2 == instance);   //false

    }
}

可见,反射确实可以干预单例模式,解决方法:修改私有构造

//获取对象
public static LazySingleton2 getInstance(){
    if(InnerClass.lazySingleton2 != null) {
        throw new RuntimeException("单例模式不允许创建多个实例...");
    }
    return InnerClass.lazySingleton2;
}

补充:在枚举类型的Class类中已经进行了处理,所以枚举类型可以防止反射工具。

2. 饿汉式

概念:饿汉式加载当类一加载完就创建对象
类加载步骤:

  1. 加载对应的二进制文件进方法区,创建对应的数据结构
  2. 连接:a.验证 b.准备(默认值的赋值) c.解析
  3. 初始化:给静态变量赋值
public class HungrySingletonTest {
    public static void main(String[] args) {
        
    }
}
class HungrySingleton{
    private static  HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值