java单例模式以及实现方式

java单例模式以及实现

参考博客:https://blog.csdn.net/qq_41458550/article/details/109243456

一.单例模式的定义:

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。

二.单例模式的特点

 1、单例类只能有一个实例。
 2、单例类必须自己创建自己的唯一实例。
 3、单例类必须给所有其他对象提供这一实例。
单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。

三.线程安全问题:

一方面在获取单例的时候,要保证不能产生多个实例对象;另一方面,在使用单例对象的时候,要注意单例对象内的实例变量是会被多线程共享的,推荐使用无状态的对象,不会因为多个线程的交替调度而破坏自身状态导致线程安全问题

四.实现单例模式的方式:

1.饿汉式(静态常量)【可用】

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

public class SingleTonE {

    //线程安全
    //单例模式,饿汉式
    private static SingleTonE instance = new SingleTonE();//先创建对象
    //私有化构造方法
    private SingleTonE(){}

    public static SingleTonE getInstance(){
        return instance;
    }
}

2.饿汉式(静态代码块)【可用】

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

public class SingleTonE {

    //线程安全
    //单例模式,饿汉式
    private static SingleTonE instance;
    //私有化构造方法
    private SingleTonE(){}
    
    static {
        instance = new SingleTonE();
    }

    public static SingleTonE getInstance(){
        return instance;
    }
}

3.懒汉式(线程不安全)【不可用】

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

public class SingleTonL {

    //线程不安全
    //单例模式,懒汉式
    private static SingleTonL newInstance = null;
    private SingleTonL(){}

    public static SingleTonL getInstance(){
        if(newInstance == null){
            newInstance =  new SingleTonL();
        }
        return newInstance;
    }
}

4.懒汉式(线程安全,同步方法)【不推荐用】

解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。对整个方法上锁效率很低,因为每次调用方法都要获取锁。

public class SingleTonL {
//线程不安全
//单例模式,懒汉式
private static SingleTonL newInstance = null;
private SingleTonL(){}

public synchronized static SingleTonL getInstance(){
    if(newInstance == null){
        newInstance =  new SingleTonL();
    }
    return newInstance;
	}
}

5.懒汉式(线程安全,同步代码块)【不可用】

上述方法对整个方法加锁,每次调用方法都会获取锁,效率低,所以要修改为对创建的对象的代码加锁,因为创建对象的代码块只要执行一次就够了。

public static SingleTonL getInstance2(){
    if(newInstance == null){
        synchronized (SingleTonL.class){
             newInstance =  new SingleTonL();
        }
    }
    return newInstance;
}

缺点:不能够保证线程安全,当多个线程同时执行了if(newInstance == null)判断之后,可能会进入同步代码块中,导致生成多个对象,从而出现线程安全问题。

6.使用双重验证的方式【推荐】

进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

//双重验证锁
public static SingleTonL getInstance2(){
    if(newInstance == null){
        synchronized (SingleTonL.class){
            if(newInstance == null){
                newInstance =  new SingleTonL();
            }
        }
    }
    return newInstance;
}

7.使用静态内部类【推荐】

使用静态内部类的方式来创建实例对象,由于静态内部类只有被调用的时候才会被加载到jvm,所以实现了懒加载,节省空间,在类初始化的时候,别线程无法对其干扰,所以jvm保证了线程的安全性。

//静态内部类
public static SingleTonL getInstance3(){
    if(newInstance == null){
        newInstance =  SingleTonLFactory.getInstance();
    }
    return newInstance;
}

五.单例模式的优缺点

优点:保证了整个系统中只有一个对象,在多线程需要频繁创建、销毁对象的情况下,使用单例模式可以提升系统性能。在一些需要控制对象实例的业务情景下,就可以使用单例模式来实现。单例模式作为通信媒介使用,也就是起到数据共享的作用,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。

缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。

六.单例模式的使用场景总结

  • 需要频繁创建的,经常使用的对象,使用单例模式可以节省空间提高性能
  • 需要数据共享的场景
  • 频繁访问数据库的文件或对象
posted @ 2021-08-30 20:47  TidalCoast  阅读(144)  评论(0编辑  收藏  举报