class Singleton{
private static volatile Singleton instance=null;//(3)volatile修饰
public static Singleton getInstance(){
if (instance==null){//(2)判定是否需要加锁
synchronized (Singleton.class) {//(1)加锁
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
private Singleton(){
}
}
单例模式是设计模式比较常见的一种场景,单例就是单个实例,约定某个类中只能有一个对象,后续不管有多少个线程创建这个类对象,都是同一个对象。单例模式的编写有饿汉和懒汉两种模式。
饿汉模式
可以理解为比较急切,事先在类中准备好这个对象
class Singleton{
private static Singleton instance=new Singleton();
public static Singleton getInstance(){
return instance;
}
private Singleton(){//类的构造方法设为私有,类外面的其他代码, 就无法 new 出这个类的对象了
}
}
这样就是一个单例模式,在main方法中测试这个类,只能通过调用类中的getInstance方法去获取这个instance实例,而不能去new一个类对象。
尝试去new的时候会报错,提示你这个类的构造方法是私有的。
public class test {
public static void main(String[] args) {
Singleton t1=Singleton.getInstance();
Singleton t2=Singleton.getInstance();
System.out.println(t1==t2);
}
}
结果为true,因为获取的是同一个实例。
懒汉模式
可以认为比较懒,什么时候使用,什么时候创建实例。
class Singleton2{
private static Singleton2 instance=null;
public static Singleton2 getInstance(){
if (instance==null){//第一次用的时候创建出实例
instance=new Singleton2();
}
return instance;
}
private Singleton2(){
}
}
针对上述两种模式,在单线程下都是安全的,但是在多线程编程下,显然懒汉模式并不安全,在这种模式下,多个线程尝试对同一个变量进行读取和修改(单纯的读取是没问题的),就会产生线程安全问题。
那么如何保证懒汉模式也是线程安全的呢。
首先,不安全的问题就出在上面两个步骤,由于多个线程对同一个变量进行读写,不同线程的两个步骤进行穿插,就会出现问题,那么我们首先就会想到一种解决方案,那就是加锁,把这两个步骤捆绑在一起。
但是这么写,后续每次调用getInstance都要加一遍锁,而其实线程不安全的问题只有第一次创建实例才有,后续一旦实例已经创建了,接下来针对这个实例只是读取操作,所以这么写反而让代码变得有些负担,所以我们进一步去判断加锁的条件,如果实例第一次创建,那么需要加锁,如果已经存在,则不需要加锁。
同时还需要考虑到编译器的指令重排序情况。编译器在new操作时,会分为几个步骤进行,编译器在优化时,为了提高效率,可能会打乱代码的执行顺序(逻辑不变的情况下),在就是指令重排序,所以为了防止这种情况,就需要在instance前面加一个volatile关键字。
这样三个步骤下来,基本就解决了懒汉模式的线程不安全问题。