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,则说明三个线程就是调用的同一个对象实例。
106547302910654730291065473029
懒汉模式
懒汉模式也称为延迟加载,就是在调用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;}}
这种懒汉模式,若使用上一步中的多线程环境中,就可能取出多个实例,也就不符合单例模式的特点了:
168654971712044368661204436866
从上边的两种单例模式来看,饿汉模式在多线程环境中是线程安全的,而对于懒汉模式来说,在多线程环境中,就是一个错误的单例模式,那么针对这个问题,该如何去处理,使得懒汉模式可以适用于多线程环境呢?
前边的博文中介绍了使用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;}
运行结果如下:
1204436866789550240669428867
三个线程输出的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)