懒汉式线程不安全
当第一次调用getInstance()方法时,如果多个线程几乎同时进入if (instance == null)的判断语句,并且此时instance确实为null,这些线程都有机会执行instance = new Test()来创建单例对象。由于构造函数没有被同步,可能会有多个线程分别执行该构造函数,从而导致多个不同的单例实例被创建
代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class scratch_7 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
Test test = Test.getInstance();
System.out.println(test);
}
});
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(2, TimeUnit.MINUTES)) {
executorService.shutdownNow();
System.out.println("强制关闭");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束");
}
}
class Test {
private static Test instance;
private Test() {}
public static Test getInstance(){
if (instance == null) {
instance = new Test();
}
return instance;
}
}
class Test2 {
}
输出结果
Test@2b47f4bb
Test@4e3058f8
Test@4e3058f8
Test@4e3058f8
Test@4e3058f8
Test@4e3058f8
Test@4e3058f8
Test@4e3058f8
Test@4e3058f8
Test@5bd863fa
懒汉式线程安全
通过加锁机制(如synchronized关键字)确保线程安全,但这可能会降低性能,因为每次调用getInstance()时都会进行同步检查
class Test {
private static Test instance;
private Test() {
}
public synchronized static Test getInstance() {
if (instance == null) {
instance = new Test();
}
return instance;
}
}
双重检查锁定
这种改进的懒汉式实现在第一次检查实例是否为null时不加锁,只在实例真正创建时同步,这提高了性能。
class Test {
private static Test instance;
private Test() {
}
public static Test getInstance() {
if (instance == null) {
// 不能是instance,因为第一次进来instance是null,会抛出异常
synchronized (Test.class) {
if (instance == null) {
instance = new Test();
}
}
}
return instance;
}
}
饿汉式
在这种模式下,单例实例在类加载时就创建,由于ClassLoader 方法在加载类时使用了 synchronized 关键字,确保了同一时间只有一个线程可以执行类的加载过程,从而避免了多线程环境下可能出现的并发问题,但如果没有使用这个实例,也会造成内存浪费
class Test {
private static final Test instance = new Test();
private Test() {
}
public static Test getInstance() {
return instance;
}
}
静态内部类
这种方式同样利用了 ClassLoader 机制来保证初始化 instance 时只有一个线程,它跟上一种方式不同的是:只要 Test 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 TestHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 TestHolder 类
class Test {
private static class TestHolder{
private static Test instance = new Test();
}
private Test() {
}
public static Test getInstance() {
return TestHolder.instance;
}
}
枚举
枚举是实现单例的一种简洁、线程安全且无需考虑反射和序列化问题的方式。枚举天然具备单例的特性,由JVM保证其唯一性,且默认就是线程安全的
public enum Test {
INSTANCE;
public void whateverMethod() {
}
}
总结
一般情况下,不建议使用懒汉式线程不安全和懒汉式线程安全,建议使用饿汉式。只有在要明确实现 lazy loading 效果时,才会使用双重检查锁定。如果涉及到反序列化创建对象时,可以尝试使用枚举。如果有其他特殊的需求,可以考虑使用双重检查锁定。