目录
前言:
单线程下的单例模式
饿汉模式
懒汉模式
多线程下的单例模式
懒汉模式的修改
1.加锁
2.有条件的加锁
3.解决内存可见性和指令重排序
前言:
本片文章介绍设计模式中的一个模式——单例模式。
单例模式就是只允许创建出一个实例的类。比如之前在使用JDBC编程的时候的DataSource这个类,就可以使用单例模式。见这篇文章http://t.csdn.cn/uq1lR。
其中单例模式有很多种实现方法,这里只用懒汉模式和饿汉模式来实现单例模式。
单线程下的单例模式
单线程中,没有线程安全问题,其代码会简单很多。
饿汉模式
饿汉模式体现出一个字——急。因为这个实例直接就是在类加载阶段就被创建出来。
class SingletonHungry{
// 用static修饰,这样在类加载阶段就有了这个实例
private static SingletonHungry instance = new SingletonHungry();
// 用静态公开方法返回实例
public static SingletonHungry getInstance() {
return instance;
}
// 为了防止实例化多个对象,把构造方法用private修饰
private SingletonHungry(){};
}
public class SingletonTest1 {
public static void main(String[] args) {
SingletonHungry instance1 = SingletonHungry.getInstance();
SingletonHungry instance2 = SingletonHungry.getInstance();
System.out.println(instance1 == instance2);
//SingletonHungry instance3 = new SingletonHungry();
}
}
懒汉模式
懒汉模式突出一个字——懒。这个实例是只有调用获取实例方法的时候才创建出来。
class SingletonLazy{
private static SingletonLazy instance = null;
// 只有在调用了该方法后才创造出了实例
public static SingletonLazy getInstance() {
// 如果已经创建了该实例,就直接返回之前的实例
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
// 为了防止实例化多个对象,把构造方法用private修饰
private SingletonLazy(){}
}
public class SingletonTest2 {
public static void main(String[] args) {
SingletonLazy instance1 = SingletonLazy.getInstance();
SingletonLazy instance2 = SingletonLazy.getInstance();
System.out.println(instance1 == instance2);
//SingletonLazy instance3 = new SingletonLazy();
}
}
代码结果如上。
多线程下的单例模式
在上述代码中,如果考虑多线程的话
饿汉模式没有线程安全问题,它直接就是在类加载是创建出了一个实例,使用该实例也没有其他修改的操作,直接返回即可。
懒汉模式是有线程安全问题。它又要判断比较实例是否为null,又要创建一个实例,最后才返回实例。其中对于实例是由修改的操作的。只要有修改操作,就可能会有线程安全问题。如下图:
懒汉模式的修改
1.加锁
对于这种又有读,又有写的操作,保持原子性——加锁即可。
public SingletonThread getInstance() {
// 这里对于load cmp new这几步加锁,保持了其原子性
synchronized (SingletonThread.class) {
if (instance == null) {
instance = new SingletonThread();
}
}
return instance;
}
2.有条件的加锁
但是加锁是一种开销比较大的操作,上述加锁操作并不是每次都要加锁的。如果已经创建了实例,直接返回实例即可。
public SingletonThread getInstance() {
// 如果实例未被创建,用加锁的方法创建实例
// 如果创建,直接返回
if (instance == null) {
// 这里对于load cmp new这几步加锁,保持了其原子性
synchronized (SingletonThread.class) {
if (instance == null) {
instance = new SingletonThread();
}
}
}
return instance;
}
3.解决内存可见性和指令重排序
内存可见性:因为instance要读取并修改,所以对于内存可见性的问题也要预防。
指令重排序:
上面的new实例的指令又分为三个顺序步骤:
①申请内存空间
②调用构造方法,实例化一个对象
③把内存空间的地址赋值给这个对象
这几步可能可能会变成①③②。单线程下没有问题,但是多线程就会有问题了。
要想解决这两个问题,使用volatile关键字修饰instance即可。
private volatile static SingletonThread instance = null;
完整的懒汉模式的线程安全代码如下:
// 修改懒汉模式,使其线程安全
class SingletonThread {
// 使用volatile解决内存可见性和指令重排序问题
private volatile static SingletonThread instance = null;
public SingletonThread getInstance() {
// 如果实例未被创建,用加锁的方法创建实例
// 如果创建,直接返回
if (instance == null) {
// 这里对于load cmp new这几步加锁,保持了其原子性
synchronized (SingletonThread.class) {
if (instance == null) {
instance = new SingletonThread();
}
}
}
return instance;
}
}
有什么问题评论区指出。希望可以帮到你。