1、最经典也是最简单的单例模式:
public class Singleton {
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、上述的单例模式是线程安全的吗?
答案当然是否,下面我们来验证下:以下代码模拟两个线程同时进入if(instance==null)判断。
public class Singleton {
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance(){
if (instance == null) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest {
@Test
public void testSingleton() throws InterruptedException {
new SingleThreadDemo().start();
new SingleThreadDemo().start();
Thread.sleep(10000);
}
}
class SingleThreadDemo extends Thread{
@Override
public void run() {
Singleton s = Singleton.getInstance();
System.out.println(s);
}
}
结果:(可见这两个对象是不同的)
3、线程安全的单例模式
3.1、使用synchronized关键字修饰getInstance方法
3.2、静态对象直接实例化
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
3.3、使用双重检查锁
public class Singleton {
private Singleton(){}
// volatile确保每个线程取到的instance都是最新的
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
// 当instance为空,才锁住Singleton类
synchronized (Singleton.class) {
// 获得当前Singleton监视器的线程再作一次instance为空的判断
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这里要使用volatile修饰instance,原因是,instance = new Singleton()可能发生指令重排序:
即:1、分配内存 2、初始化对象 3、设置instance指向内存地址 这三个步骤2和3有可能颠倒顺序,导致当多线程判断if(instance==null)时,有可能其中一个线程拿到还没有初始化但是已经有内存地址的instance对象。
如果有volatile,则能够使instance初始化后刷新到系统内存,而其他线程判断本地内存(缓存)中的instance和当前内存中的instance是否一致,如果不是则使缓存失效,重新读取。
还有一个神奇的现象,在JDK1.2之前,如果没有其他类引用本单例对象,本单例对象就会被垃圾回收,但是在JDK1.2之后,这个bug修复了