您的位置:首页 > 其它

详解单例设计模式的线程安全问题

2018-02-13 13:11 225 查看

1. 饿汉模式

/**
* 饿汉模式
*/
public class Singleton {
private static Singleton instance = new Singleton();

private Singleton() {
}

public static Singleton getInstance() {
return instance;
}

public static void func() {
System.out.println("this is a function!");
}
}
优点
线程安全、绝对单例
缺点:
在多实例或者有其他静态方法时,instance在没有使用它的时候就已经加载好了。
例如调用Singleton.func(),这个方法中并没有用到instance,但是instance也被初始化了。若该对象很大,则在没使用时就加载很浪费内存。
不能防止反序列化、反射产生新的实例。

2. 懒汉模式,延时加载

/**
* 懒汉模式
*/
public class Singleton {
private static Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {
if (null == instance) { // ①
instance = new Singleton();
}
return instance;
}

}
优点:
instance在没有使用时不用加载,节省内存。
缺点:
线程不安全;
线程不安全原因:当多个线程执行到①时,都是发现instance为null,于是都去初始化了。
不能防止反序列化、反射产生新的实例。

3. 方法锁

/**
* 方法锁
*/
public class Singleton {
private static Singleton instance = null;

private Singleton() {
}

public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}

}
优点:
线程安全,绝对单例
缺点:
加锁导致程序并发性降低;
不能防止反序列化、反射产生新的实例。

4. 双重检查锁 DCL

/**
* 双重检查锁DCL
*/
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;
}

}
优点:
大部分时候线程安全,相比方法锁程序并发性较高。
缺点:
线程并非绝对安全;
不能防止反序列化、反射产生新的实例。
线程不安全原因:
Java的乱序执行、初始化对象需要时间。
对于语句③,JVM在执行时大致做了下述三件事:
a. 在内存中分配一块内存
b. 调用构造方法
c. 将内存的地址指向instance变量。(执行这一步后,instance != null)
如果按照abc的顺序执行也不会有什么问题。但由于Java乱序执行的机制,有可能在真实情况下执行顺序为acb。
假设t1、t2是两个线程。t1执行到①时,发现为null,于是执行到语句③,先执行a,再执行c,在还没有执行b时,时间片给了t2。这时,由于instance已经分配了地址空间,instance不为null了。所以t2在执行到语句①后直接return instance,获得了这个还没有被初始化的对象,然后在使用时就报错了。
也就是说,在使用DCL时,有可能得到了实例的正确引用,但却访问到其成员变量的不正确值。

5. 双重检查锁DCL与volatile的联用

/**
* 双重检查锁DCL与volatile的联用
*/
public class Singleton {
private static volatile Singleton instance = null;

private Singleton() {
}

public static Singleton getInstance() {

if (null == instance)
4000
{
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton(); // ③
}
}
}
return instance;
}

}
优点:
线程安全、绝对单例
缺点:

代码看起来有点乱;
不能防止反序列化、反射产生新的实例
线程安全的原因:

我纳闷这个很久,后来发现我只关注了volatitle的一个特性:对变量的读取每次都是从主存中读取。volatitle还有另外一个特性:volatile屏蔽指令重排序。也就是说,执行语句③时,肯定是按照abc的顺序执行的(参见四)。

6. 静态内部类 Initialization On Demand Holder

/**
* 静态内部类 Initialization On Demand Holder
*/
public class Singleton {

private static class SingletonHolder{
static final Singleton instance = new Singleton();
}

private Singleton() {
}

public static Singleton getInstance() {
return SingletonHolder.instance;
}

}
优点:
线程安全,绝对单例
缺点:
不能防止反序列化、反射产生新的实例
和饿汉模式的区别:饿汉模式会在没有使用它的时候就已经加载实例。
线程安全的原因:Java的运行机制保证了static修饰的类、对象仅被初始化一次。

7. 枚举类

public enum Singleton {
instance {
String name = "this is name";

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}
};

public abstract void setName(String name);
public abstract String getName();
}
优点:
线程安全、绝对单例、可以防止因为反序列化、反射产生新的实例
缺点:
和饿汉模式类似,会造成内存浪费
本文转载于一篇博客http://blog.csdn.net/h28496/article/details/49884469,写得比较全面具体,笔者转载之,推荐学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息