单例模式概念
确保某一个类只有一个实例。
懒汉式,线程不安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
懒汉式,线程安全
将整个 getInstance() 方法设为同步(synchronized)。
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。影响性能。
懒汉式,双重检验锁
双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。
为什么在同步块内还要再检验一次?
因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if(singleton == null){
synchronized(Singleton.class){
// 当两个以上的线程同时进入第一次空检查时,为避免实例多次,需要再次检查。
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
但是还是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1、给 instance 分配内存。
2、调用 Singleton 的构造函数来初始化成员变量。
3、将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。
但是在 JVM 编译后可能存在指令重排序的优化。
有可能先执行第3步,再执行第2步。
如果在第3步 执行完毕的情况下、且第2步未执行之前,被其他线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,因为没有初始化,所以在使用的过程中报错。
只需要将 instance 变量声明成 volatile 就可以了。
volatile 关键字功能:
1、保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2、禁止进行指令重排序。
public class Singleton {
private volatile static Singleton instance; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。
但是事实上,这段代码会完全运行正确么?
即一定会将线程中断么?
不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。
JVM每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:
使用volatile关键字会强制将修改的值立即写入主存。
饿汉式,线程安全
单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
静态内部类实现单例
使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷。实例被声明了 static 和 final 类型,在第一次加载就会被初始化。
public class Singleton {
private Singleton() { }
public static Singleton getInstance() {
return SingletomHolder.INSTANCE;
}
/** 写一个静态内部类,里面实例化外部类 */
private static class SingletomHolder {
// INSTANCE 是常量,因此只能赋值一次;它还是静态的,因此随着内部类一起加载。
private static final Singleton INSTANCE = new Singleton();
}
}
本文深入探讨了单例模式的不同实现方式,包括懒汉式、饿汉式和静态内部类实现,详细解析了线程安全问题及其解决方案,如双重检查锁和volatile关键字的作用。

被折叠的 条评论
为什么被折叠?



