单例模式
单例模式,懒汉模式,double check
1 | public class Singleton{ |
私有化构造方法,增加用户直接构造对象的难度。(用户还是可以通过反射的方法构造)
static
变量指向该静态对象,保证该对象创建后不会被回收。 一个对象是否被回收,取决于有没有gc root的变量指向它(直接或间接)。而常见的gc root有:- class 由System Class Loader加载的类
- 方法栈中的变量
- 活着的线程Thread
volatile
确保该变量指向的对象在线程之间的可见性,防止在5
中对象会创建多个。创建一个对象有三个步骤:
- 为该对象开辟内存
- 创建该对象
- 令变量指向该对象地址
在对象创建过程中,为了效率考虑,三个步骤的顺序是可以打乱重排的(三者顺序不确定),由于默认重排序的机制存在,在本例子中,在
5
中,可能还会出现创建多个对象的情况。volatile
关键字有两个作用,禁止编译器为优化而指令重排,保证该变量指向对象的线程之间的可见性。
什么是线程之间可见性?
原本,不同线程中的变量是可以指向堆中同一个对象,若是有一个线程修改了该对象,其它线程按理说是可以马上获取到该改动的,因为它们的指向都没变嘛。可是,出现的问题在于变量与内存之间,还多个了个缓存。且,每个线程在读取内存中的对象实例后,都是放到它的私有缓存中,之后的修改也是修改该缓存中的实例对象。所以,当线程A修改对象时,线程B并没有收到A中的修改。即,我们可以称该现象为线程之间的不可见。
而volitale
修饰的变量,
读取该变量时,先同步主内存的修改。
写操作时,将该修改同步到主内存。
这样,通过读写操作,即可保证该线程之间的可见性。
这次判空检查,避免多个线程检验锁,(检验锁,若是没有获取到该对象锁,即会将该线程添加到该对象的锁等待池中。)这里为性能考虑。
使用Singleton.class对象做为对象锁,而不是其他对象,因为该对象是唯一的。
- sync 可以修饰在不同地方,比如静态方法,成员方法,锁住一个对象,但本质都是锁一个对象
- 若是在静态方法,就是锁该Class类对象。
- 若是成员方法,就是当前调用方法的变量了。
- 当然了,我们也可以显式指定对象。比如这样,sync(object)
这次判空是要保证该对象的唯一。 若是多个线程从该锁对象的等待池中醒了过来,需要判空一下。当然了,需要加载volatile的判空处理。
静态内部单例
1 | public class Singleton{ |
额外:
非静态内部类, 为啥不能声明static变量?
答:因为这个内部类需要外部类的一个对象来指向, 反过来说,就是该内部类持有一个外部类实例的成员。即通过A().B.salary。 而静态内部类可以,是因为该内部类,没有持有外部类的应用,所以可以直接通过A.B.salary.