相信大家都对单例模式非常熟悉了,可以利用单例模式重复使用某个对象。有一次面试的时候,问到了单例模式,终于明白自己对线程模式下的单例模式不是很了解。饿汉式单例模式是线程安全的,但是它在加载类时就创建实例,不管实例用不用地到,考虑到效率问题,所以个人更喜欢懒汉式单例模式(延迟加载),于是面试题就根据懒汉式单例模式展开。
首先,我们来写一个单线程下的懒汉式单例模式
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
这个很简单
Q:写一个多线程下的单例模式
A:balala......
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (this) {
if (null == instance) {
instance = new Singleton();
}
}
return instance;
}
}
Q:为什么同步加在对象上,不加在方法上?
A:因为同步的过程中会持有锁,其他线程在获取锁的过程中需要等待,所以同步粒度越小越好。
以为这个题目很顺利的过去了,没想到悲剧开始上演。Q:为什么要用同步this?
A:额,好像错了,this无法在类方法使用,那改同步instance。
Q:instance没创建,能持有它的内置锁?
A:好像不行...
Q:如果改成Singleton.class呢?
A:好像是这样
Q:那类锁和对象锁有区别吗?
A:这个我知道,这两个锁无关。持有类锁,别的线程同样可以获取该类的对象锁。
后来,就改成了这样
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
return instance;
}
}
A:如果我不想每次都去获取锁,再判断实例是否为空呢?每次获取类锁,会导致性能一定的下降
Q:可以使用双检锁。
public class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if(null == instance){
synchronized(Singleton.class){
if(null == instance){
instance = new Singleton();
}
}
}
return instance;
}
}
Q:不过双检锁有一个问题,就是对象在初始化的时候,有两步,第一步,系统默认值的初始化,第二步才是构造方法初始化。在系统默认值初始化完毕以后,实例instance不为null,另外的线程可能会直接发现instance不为null,继续执行后面的代码,但是实际上instance通过构造方法做的初始化还没执行,在多线程里,这叫不正确的发布对象。可以这样做。
public class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if(null == instance){
synchronized(Singleton.class){
if(null == instance){
Singleton temp = new Singleton();
instance = temp;
}
}
}
return instance;
}
}