单例模式

    xiaoxiao2022-07-02  95

    文章目录

    概念 & 原理1. 定义2. 适用场景3. 优缺点4.单例模式的重点内容 懒汉式案例多线程debug验证线程安全1. 情况一调试2. 情况二调试3. 解决方法

    概念 & 原理

    1. 定义

    保证一个类仅仅有一个实例,并提供一个全局访问点类型:创建型

    2. 适用场景

    想确保任何情况下都绝对只有一个实例实际场景:数据库连接池

    3. 优缺点

    优点:在内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用;设置了全局访问点,严格控制访问(对外不让new)缺点:没有借口,扩展困难

    4.单例模式的重点内容

    私有构造器线程安全延迟加载序列化和反序列化反射

    懒汉式案例

    public class Singleton { //懒汉式 private static Singleton uniqueInstance; private Singleton(){} //保证线程安全 public static synchronized Singleton getInstance(){ if(uniqueInstance==null) uniqueInstance = new Singleton(); return uniqueInstance; } } public class Application { public static void main(String[] args) { // TODO Auto-generated method stub Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } }

    运行结果:

    多线程debug验证线程安全

    package com.rjxy.demo1; public class Application { /** * @param args */ public static void main(String[] args) { Thread t1 = new Thread(new T()); Thread t2 = new Thread(new T()); t1.start(); t2.start(); System.out.println("end..."); } } class T implements Runnable{ @Override public void run() { Singleton singleton = Singleton.getInstance(); System.out.println(Thread.currentThread().getName()+" "+singleton.hashCode()); } }

    开启debug模式:

    可以发现,三个线程在三个断点处都已经停了下来,suspend表示挂起

    1. 情况一调试

    让线程 0 先进入 if 语句,但不要让线程 0 为 uniqueInstance 赋值: 让线程 1 也进去,让其为 uniqueInstance 赋值: 回到线程 0 ,让其也为 uniqueInstance 赋值: 线程 1 还没有返回,在这种情况之下,uniqueInstance 的返回值是最后的那个创建实例的线程: 在这种情况下,线程生成了不止一个实例,但之所以输出相同,是概率原因。如果有很多线程的话,且生成对象需要消耗资源,那么很可能导致系统故障

    2. 情况二调试

    在情况一的最后,不再回到线程 0 为 uniqueInstance 赋值,而是先线程一直接返回输出。 线程 1 输出后,线程 0 创建对象输出 可以看到,单例模式竟然创建了两个对象!存在线程安全问题

    3. 解决方法

    synchronized关键字。需要注意的是如果加在静态方法上锁的是这个类的class文件。如果不是静态方法,相当于锁的堆内存中生成的对象。 //保证线程安全 public static synchronized Singleton getInstance(){ if(uniqueInstance==null) uniqueInstance = new Singleton(); return uniqueInstance; }

    缺点:synchronized 存在加锁和解锁的开销。而且锁的是class文件,范围比较大,对系统性能有一定影响。

    DoubleCheck双重检查实战原理 下面这种方式将加锁推迟到if语句之后,带来的好处就是在实例被创建出来之后,获取该实例的时候,就不必大费周章地去加锁解锁,节约资源。但是如果是多线程的话,就有大问题了。 uniqueInstance = new Singleton();这句代码要经过:①分配内存空间、②初始化对象、③将索引指向该内存空间。其中②和③在多线程的情况下会发生重排序,造成异常的出现,当然这是有概率的,并不绝对。解决办法就是 volatile 关键字,这个关键字会避免重排序的发送。关于重排序、volatile原理我会在后面的博客中讲到。 public class Singleton { //懒汉式 private volatile static Singleton uniqueInstance; private Singleton(){} //保证线程安全 public static synchronized Singleton getInstance(){ if(uniqueInstance==null) { synchronized(Singleton.class) { uniqueInstance = new Singleton(); } } return uniqueInstance; } }
    最新回复(0)