java实现线程安全的单例模式
⼀、平时使⽤的软件中,例如回收站、线程池、⽂件系统等,都只有⼀个实例,这些都是单例模式的典型应⽤。
单例模式:确保某个类只有⼀个实例,并提供⼀个全局访问点来访问这个实例。
单例模式有三个要点:
1. 某个类只能有⼀个实例
2. 必须⾃⾏创建这个实例
3. 必须⾃⾏向整个系统提供这个实例。
以上三个要点提⽰着我们的代码编写需要注意,构造函数必须私有,否则在其他类中便可以调⽤构造函数创建实例,难以保证实例的唯⼀性。
⼆、单例模式分为饿汉模式和懒汉模式
//饿汉模式:(线程安全)
public class Singleton1 {
// 静态私有成员变量
private static Singleton1 instance = new Singleton1();
// 私有构造函数
private Singleton1() {
}
// 静态公有⼯⼚⽅法,返回唯⼀实例
public static Singleton1 getInstance() {
return instance;
}
}
/
/ 懒汉模式:(线程不安全,需要通过双重检查锁定机制控制)
public class Singleton2 {
// 静态私有成员变量
private static Singleton2 instance = null;
// 私有构造函数
private Singleton2() {
}
// 静态公有⼯⼚⽅法,判断成员变量是否为空,不为空则实例化
public static Singleton2 getInstance() {
if(instance == null)
instance = new Singleton2();
return instance;
}
}
优缺点:
饿汉模式不需要考虑线程安全问题,调⽤速度和访问速度优于懒汉模式,但是由于它不管是否被调⽤都会提前创建类的实例,所以资源利⽤效率⽐较低,系统加载时间⽐较长。
懒汉模式实现了延迟加载,但是需要克服多个线程同时访问的问题,需要通过双重检查锁定机制进⾏控制,导致系统性能受到⼀定影响。
三、下⾯两个⽅法实现懒汉模式的线程安全。
为什么会线程不安全?
假设有两个线程 A B,其中 A 执⾏到检查⽅法,即 if(instance == null) 前,实例都没有被创建,那么 A 会得到 ture 的结果,但是此时调度算法选择 B 线程运⾏,那么当 B 执⾏到 if(instance == null) 时
得到的也是 true,那这就很尴尬了,两个线程都会执⾏ instance = new Singleton2(); 从⽽创建了两个实例。
1.双重检查锁定机制。
为了避免以上这种尴尬的情况,需要将这两⾏代码加上同步锁。但这还不够完美,每次调⽤函数得到实例都要试图加上⼀个同步锁(类锁),⽽加锁是⼀个⾮常耗时的操作,没有必要的情况下应该尽量避免。基于这种想法,我们可以在加锁前再次判断实例是否为空。这就是双重检查锁定机制。
还有⼀点,定义 instance 变量时需要使⽤ volatile 进⾏修饰,因为需要保证 instance 变量发⽣修改后可以及时将结果刷新到主内存中,对其他线程可见。
public class Singleton3 {
// 私有静态成员变量
private static volatile Singleton3 instance = null;
// 私有构造函数
private Singleton3() {
}
// 共有静态⼯⼚⽅法
public static Singleton3 getInstance() {
// 判断 instance 是否为空,为空->加锁,创建实例(为了进程安全,再次判断),不为空->返回实例
if(instance == null) {
synchronized (Singleton3.class) {
if(instance == null)
instance = new Singleton3();
}
}
return instance;
单例模式的几种实现方式
}
}
2. 使⽤静态内部类创建实例。(JAVA 语⾔中最好的实现⽅法)
public class Singleton4 {
// 私有构造函数
private Singleton4() {
}
// 静态内部类
private static class HolderClass{
private static final Singleton4 instance = new Singleton4();
}
// 静态公有⼯⼚⽅法,返回内部类中创建的实例
public static Singleton4 getInstance() {
return HolderClass.instance;
}
}
加载 Singleton4 类的过程中,会为类变量在⽅法区中分配内存空间并初始化,但是,并不会加载静态内部类 HolderClass。
当调⽤ Instance() ,执⾏return HolderClass.instance语句时,HolderClass 类才会被加载,instance 对象才会被初始化。
上⾯的解释说明使⽤静态内部类创建实例是懒加载的; HolderClass 只会加载⼀次,保证了 instance 是单例的;类加载过程是线程安全的,保证 instance 初始化的过程是线程安全的。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论