并发之懒汉饿汉的单例模式线程安全问题
程序员文章站
2022-05-04 20:59:54
...
饿汉模式:
本身线程安全,在类加载时就已经进行了实例化,无论之后用不用的到。
package com.cljtest.demo.thread;
public class HungerSingleton {
public static HungerSingleton hungerSingleton = new HungerSingleton();
public static HungerSingleton getInstance(){
return hungerSingleton;
}
//构造方法私有化
private HungerSingleton(){
}
public static void main(String[] args) {
//起10个线程进行结果测试
for(int i = 0;i<10;i++){
new Thread(()->{
System.out.println(HungerSingleton.getInstance());
}).start();
}
}
}
一定要注意无参构造方法的私有化问题,如果
设成了public,其他类里实例化时直接new一个无参的构造方法,会造成单例模式失去意义。
由于类加载时就已经实例化了,所以下面的的一切都是基于类加载时创建的对象进行的操作,所以线程安全,都是同一个实例化对象。
运行结果如下:
可以看到地址都是同一个,没有改变。
懒汉模式:
是在需要的时候才实例化,最简单的写法是线程不安全的。
package com.cljtest.demo.thread;
public class LazySingleton {
public static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public static LazySingleton getInstance() throws InterruptedException {
if(null == lazySingleton){
//模拟操作中的停顿
Thread.sleep(1000L);
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
public static void main(String[] args) {
for(int i = 0;i<10;i++){
new Thread(()->{
try {
System.out.println(LazySingleton.getInstance());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
可以看到结果,地址几乎均不相同,每次的实例化都是一个新的对象,线程是不安全的。
如果想要将懒汉模式变成线程安全的,需要对实例化的操作进行加锁处理。需要将getInstance()方法进行如下修改。
public static LazySingleton getInstance() throws InterruptedException {
if(null == lazySingleton){
//模拟操作中的停顿
Thread.sleep(1000L);
//为了线程安全,需要加锁
synchronized (LazySingleton.class) {
//双重判定,对象没实例化时再创建
if(null == lazySingleton) {
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
这时候运行会发现基本没有问题了,输出的地址都是一个。
但是对这个类进行反编译可能会进行指令重排序的操作,也会造成线程的不安全,所以需要将变量加上volatile关键字进行修饰,他有禁止指令重排序的作用。
public static volatile LazySingleton lazySingleton = null;