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,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
六.单例模式的使用场景总结
- 需要频繁创建的,经常使用的对象,使用单例模式可以节省空间提高性能
- 需要数据共享的场景
- 频繁访问数据库的文件或对象