Singleton单例设计模式

本文介绍了单例设计模式,确保一个类只有一个实例并提供全局访问点,适用于业务逻辑限定单例的场景。详细阐述了饿汉式、懒汉式(包括多种线程安全的实现)、注册式、枚举类和序列化与反序列化单例模式,还提及了相关测试及应用,如JDK的Runtime类和Spring单例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  文章最前: 我是Octopus,这个名字来源于我的中文名--章鱼;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github ;这博客是记录我学习的点点滴滴,如果您对 Python、Java、AI、算法有兴趣,可以关注我的动态,一起学习,共同进步。

相关文章:

  1. Command命令设计模式
  2. Singleton单例设计模式
  3. Decorator装饰设计模式
  4. Template模板模式
  5. Strategy策略设计模式
  6. Proxy代理设计模式
  7. Factory工厂类设计模式
  8. 设计模式的6大基本原则

文章目录:

1.饿汉式:

2.懒汉式:

3.注册式设计模式(Spring中就是利用这种方式): 

4.枚举类单例模式: 

5.序列化与反序列化单例模式: 


         单例设计模式确保一个类只有一个实例,并提供一个全局访问点。所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。


1.饿汉式:

public class Singleton {  
    private static Singleton singleton = new Singleton();  
    private Singleton(){}  
    
    public static Singleton getInstance(){  
        return singleton;  
    }  
}  
饿汉式单例 (线程安全的单例设计模式):
它是在类加载的时候就立即初始化,并且创建单例对象;
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好;
缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间,浪费了内存资源;
绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题;

2.懒汉式:

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
   
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

       但是它是线程不安全的单例设计模式,要把它改为线程安全的单例设计模式,需要加上同步关键字(synchronized)。

线程安全的懒汉式2:

public class Singleton {  
    private static Singleton singleton;  
    private Singleton() {}  
      
    public static synchronized Singleton getInstance() {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        return singleton;  
    }  
}  

         这种线程安全的懒汉式,要加锁,但是效率太低了,每次都需要加锁。

一种效率高且线程安全的懒汉式3:

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
	if (singleton == null) {
	    synchronized (Singleton.class) {
		    if (singleton == null) {
		        singleton = new Singleton();
		    }
	    }
	}
	return singleton;
    }
}

       其中使用volatile关键字,在高并发的场景下,当一个线程实例化了之后,volatile关键字 ,将该线程实例化,让其他线程可以看到,提供程序的效率。

一个利用内部类使用的线程安全的懒汉式4:

public class LazyThree {
    private boolean initialized = false;
    // 默认使用LazyThree的时候,会先初始化内部类
    // 如果没使用的话,内部类是不加载的
    private LazyThree(){
        synchronized (LazyThree.class){
            if(initialized == false){
                initialized = !initialized;
            }else{
                throw new RuntimeException("单例已被侵犯");
            }
        }
    }
    //每一个关键字都不是多余的
    //static 是为了使单例的空间共享
    //保证这个方法不会被重写,重载
    public static final LazyThree getInstance(){
        // 在返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    //默认不加载
    private static class LazyHolder{
        private static final LazyThree LAZY = new LazyThree();
    }
}

懒汉式单例(使用内部类的方式)5:
    特点:在外部类被调用的时候内部类才会被加载
    内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题;这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题;完美地屏蔽了这两个缺点;史上最牛B的单例模式的实现方式。

测试线程安全性问题:

public class ThreadSafeTest {
    public static void main(String[] args) {
        int count = 200;
        //发令枪,我就能想到运动员
        CountDownLatch latch = new CountDownLatch(count);
        long start = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        try {
                            // 阻塞
                            // count = 0 就会释放所有的共享锁
                            // 万箭齐发
                            latch.await();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        //必然会调用,可能会有很多线程同时去访问getInstance()
                        Object obj = LazyOne.getInstance();
                        System.out.println(System.currentTimeMillis() + ":" + obj);

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start(); //每循环一次,就启动一个线程,具有一定的随机性
            //每次启动一个线程,count --
            latch.countDown();
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start));
        // CountDownLatch 并不是这样子用,实际应用场景中不要学老师这样投机取巧
        // Color.INSTANCE.getInstance();
    }
}

3.注册式设计模式(Spring中就是利用这种方式): 

//Spring中的做法,就是用这种注册式单例
public class BeanFactory {

    private BeanFactory() {
    }
    //线程安全
    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {

        if (!ioc.containsKey(className)) {
            Object obj = null;
            try {
                obj = Class.forName(className).newInstance();
                ioc.put(className, obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        } else {
            return ioc.get(className);
        }
    }
}

4.枚举类单例模式: 

public enum RegiterEnum {
    INSTANCE,BLACK,WHITE;
    public void getInstance(){}
}

测试枚举单例模式:

public class ColorTest {
    public static void main(String[] args) {
        System.out.println(Color.RED);
    }
}

5.序列化与反序列化单例模式: 

//反序列化时导致单例破坏
public class Seriable implements Serializable {

    public  final static Seriable INSTANCE = new Seriable();
    private Seriable(){}

    public static  Seriable getInstance(){
        return INSTANCE;
    }
    private  Object readResolve(){
        return  INSTANCE;
    }
}

 测试序列化与反序列化模式:

public class SeriableTest {
    public static void main(String[] args) {

        Seriable s1 = null;
        Seriable s2 = Seriable.getInstance();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("Seriable.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("Seriable.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Seriable)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

序列化:

      就是说把内存中的状态通过转换成字节码的形式
      从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
      内存中状态给永久保存下来了

 反序列化:
    讲已经持久化的字节码内容,转换为IO流
    通过IO流的读取,进而将读取的内容转换为Java对象
    在转换过程中会重新创建对象new


 jdk中的Runtime类其实也是一种单例模式;Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值