java 单例模式与多线程

什么是单例模式

单例模式是一种创建型模式,某个类在采用了单例模式,在该类创建后,只能产生一个实例供外部访问,并且提供一个全局的访问点。数据库连接池的设计一般就是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要就是节省打开或关闭数据路连接所引起的效率损耗,使用单例模式,就可以大大降低这些损耗

饿汉模式

饿汉模式也称为立即加载,即就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化,也就是在调用方法前,实例已经被创建了,如下实例。

 
 
public class MyObject {
//立即加载方式/饿汉模式
private static MyObject myObject = new MyObject();
private MyObject() {}
public static MyObject getInstance() {
return myObject;
}
}

这种方式就是单例模式的实现,在以后的使用中只需要调用MyObject.getInstance()方法即可。对于这种模式,使用多线程实现调用。

 
 
//创建线程类
public class MyThread extends Thread {
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}

创建运行类,创建三个线程去分别调用MyObject类中的getInstance方法,也就是MyObject的实例:

 
 
//运行类
public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
 
t1.start();
t2.start();
t3.start();
}
}

运行三个线程,可以看到结果输出了三个相同的hashcode,则说明三个线程就是调用的同一个对象实例。

 
 
1065473029
1065473029
1065473029
懒汉模式

懒汉模式也称为延迟加载,就是在调用get()方法时才被创建,常见的实现办法就是在get()方法中进行new实例化。实例如下:

 
 
public class MyObject {
private static MyObject myObject;
private MyObject(){}
 
public static MyObject getInstance() {
//懒汉模式
if(myObject != null){
//一些操作
}else {
myObject = new MyObject();
}
return myObject;
}
}

这种懒汉模式,若使用上一步中的多线程环境中,就可能取出多个实例,也就不符合单例模式的特点了:

 
 
1686549717
1204436866
1204436866

从上边的两种单例模式来看,饿汉模式在多线程环境中是线程安全的,而对于懒汉模式来说,在多线程环境中,就是一个错误的单例模式,那么针对这个问题,该如何去处理,使得懒汉模式可以适用于多线程环境呢? 
前边的博文中介绍了使用synchronized关键字可以对代码块或者方法加锁,实现多线程的同步问题,那么我们当然也可以在单例模式的多线程环境中使用它实现线程安全。

 
 
//声明synchronized同步方法
public class MyObject {
private static MyObject myObject;
private MyObject(){}
 
synchronized public static MyObject getInstance() {
//懒汉模式
if(myObject != null){
//一些操作
}else {
myObject = new MyObject();
}
return myObject;
}
}

同步代码块

 
 
public class MyObject {
private static MyObject myObject;
 
private MyObject() {
}
 
public static MyObject getInstance() {
try {
//这个位置使用synchronized和方法加锁等同
synchronized (MyObject.class) {
// 懒汉模式
if (myObject != null) {
// 一些操作
} else {
Thread.sleep(3000);
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}

以上这两个方式都可以实现懒汉模式在多线程环境下的线程安全,但是它们的运行效率都非常低,还需要继续修改代码进行完善。synchronized代码块同步,可以针对某些重要的代码单独进行,而其他代码则不需要,那么将上例中的同步代码块再次缩小一点,是否可以支持多线程下的懒汉模式呢?还是使用实例进行测试,如下:

 
 
private static MyObject myObject;
private MyObject() {}
public static MyObject getInstance() {
try {
// 懒汉模式
if (myObject != null) {
// 一些操作
} else {
Thread.sleep(3000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}

运行结果如下:

 
 
1204436866
789550240
669428867

三个线程输出的hashcode都不一致,说明产生了三个不同的实例对象,那么这就是一种错误的单例模式。 
DCL双检查锁机制 
DCL(Double-checked Locking)是用来在多线程环境下懒汉模式的单例模式中避免同步开销的一个方法。针对前边的多线程中使用synchronized关键字的方式,再次进行优化,代码示例如下:

 
 
public class MyObject {
private volatile static MyObject myObject; //添加volatile修饰符
private MyObject() {
}
 
public static MyObject getInstance() {
try {
// 懒汉模式
if (myObject != null) {
} else {
// 模拟在创建对象之前做一些准备性的工作
Thread.sleep(1);
synchronized (MyObject.class) {
if(myObject == null) //增加一次判断
myObject = new MyObject();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myObject;
}
}

再次运行代码,就可以发现,使用双重检查锁功能,成功了解决了懒汉模式遇到多线程的问题。其中需要注意使用volatile修饰符对成员myObject进行修饰,为的是防止在synchronized代码块中的myObject = new MyObject();语句中的代码重排序(在JVM中这一条语句可能有两种可能顺序,参考这篇博文http://yizhenn.iteye.com/blog/2294121)。DCL是大多数多线程结合单例模式使用的解决方案,使用双检测机制既保证了不需要同步代码的异步执行性,又保证了单例的效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值