深入java多线程,单例设计模式

1、概念

单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

单例模式三大要点:

  1. 一个类只能有一个对象实例
  2. 必须由自身创建这个实例
  3. 必须对整个系统提供这个实例,需要有对外提供实例的方法

单例模式分为饿汉式和懒汉式:

  1. 饿汉式:在程序启动或类被加载的时候,同时创建好对象实例。
  2. 懒汉式:类被加载时不进行创建,当实例被使用时才创建实例,此处需判断是否已经创建过,创建过则不用再次创建,返回即可。

2、代码实现

单例模式如何保证只有一个实例?要创建实例必须通过类的构造方法来创建,那么,只要保证构造方法不能被其他类调用,构造方法私有化即可。必须对整个系统提供这个实例,所以必须有对外提供实例的方法,且这个方法为static静态方法,在懒汉式中需要对实例判断,是否需要创建。

饿汉式单例模式:

/** 饿汉式单例,立即加载**/
public class Singleton1 {

	//构造方法私有化
	private Singleton1(){ }
	//类加载时创建实例
	private static Singleton1 instance = new Singleton1();
	//对外提供实例
	public static Singleton1 getInstance() {
		return instance;
	}
}

懒汉式单例模式:

/** 懒汉式单例,调用时才创建 **/
public class Singleton2 {
	//创建好空实例
	private static Singleton2 instance = null;
	//构造方法私有化
	private Singleton2(){ }
	//对外提供实例
	public static Singleton2 getInstance() {
		if(instance == null) 
			instance = new Singleton2();
		return instance;
	}
}

测试:

可以看出,每次获取到的实例为同一个。

 

3、多线程下的单例模式

在上篇中提到了发生多线程问题的3个条件,1是处于多线程环境下,2是多个线程共享一个资源,3是对资源进行非原子性操作。看下上述代码是否有线程安全性问题。

饿汉式代码中没有对资源进行非原子操作,不管多少个线程同时访问都不会出现线程安全问题,线程访问获取饿汉式单例方法只是读取操作,没有其他新建操作,所有没多线程安全问题。

上述懒汉式代码中,满足了出现多线程问题的3个条件,多个线程同时访问获取单例的方法,当有两个或者两个以上线程同时判断单例是否为空,当为空时,则会有多个线程创建新的实例,出现了多个实例,所以上述代码在多线程情况下会有问题。下面用多线程环境来测试代码,看是否会出现问题:

创建多个线程来获取单例,发现有一个实例跟其他实例不一样,出现了多个实例,即出现了多线程问题。

 

下面来改进代码,避免多线程问题:

在上篇中介绍了synchronized的原理,synchronized用来修饰方法或者代码块,以保证线程同步,所以只要用synchronized修饰多线程调用的获取实例的方法,就可以避免多线程问题,代码如下:

public class Singleton3 {
	//构造方法私有化
	private Singleton3() {}
	//创建空实例
	private static Singleton3 instance = null;
	//对外提供实例
	public static synchronized Singleton3 getInstance() {
		if(instance == null)
			instance = new Singleton3();
		return instance;
	}
}

再来测试,用线程池创建20个线程多次测试:

测试发现每次获取到的实例为同一个,没有出现多线程问题。

不过,使用synchronized对整个方法同步,加锁力度大,并发性能低,所有线程都会阻塞在方法外边。只对产生多线程问题的代码块进行同步,降低加锁力度,对instance = new Singleton3();这行代码同步,同时,在同步代码块中需要再次对instance进行空判断:

public class Singleton3 {
	// 构造方法私有化
	private Singleton3() { }
	// 创建空实例
	private static Singleton3 instance = null;
	// 对外提供实例
	public static Singleton3 getInstance() {
		if (instance == null) {
			synchronized (Singleton3.class) {
				if(instance == null) {
					instance = new Singleton3();
				}
			}
		}
		return instance;
	}
}

上述方式也叫双重检查,进行了两次空检查,这也是比较完美的多线程实现方式,但是,上述代码是否还有缺陷,是否能完全避免多线程问题呢,no,还是有可能会出现多线程问题,但是很难模拟。因为有指令重排序。

操作系统为了提高运行效率,会对java字节码进行重排序,就是不一定会按照锁编写代码的顺序执行,有可能会先进行new操作,再进行空判断操作。怎么避免指令重排序呢,就是使用volatile关键字,修饰instance变量,private static volatile Singleton3 instance = null,这样就可以避免指令重排序,避免出现多线程安全问题。

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值