跳至主要內容

Java 创建线程安全的单例 Singleton

JI,XIAOYONG...大约 4 分钟

简介

在编码中常常会用到单例,来确保类有一个唯一的对象,一般情况下将构造方法私有化既可以实现,但当考虑到多线程时事情会变得有些复杂,本文讨论的正是几种多线程的情况下实现单例的方法。

1.普通单例

私有化构造方法,对外提供一个公有、静态的方法,在其内部判断类对象是否已经存在,否的话生成类对象再返回。

class ASingleton{

	private static ASingleton as;

	private void ASinleton() {
		System.out.print("ASingleton init!\n");
	}

	public static ASingleton getInstance() {
		if(as == null) {              //tag1
			as = new ASingleton();    //tag2
		}
		return as;
	}
}

但是,在考虑多线程时,由于 java 代码是一行行进行的,假设有两个线程 t1、t2,当 as 为 null 的时候 t1 执行到 tag1 位置,判断为 true,于是准备执行 tag2,就在此时,cup 调度 t2 开始执行 tag1,此时 t1 尚未执行 tag2,所以在 t2 中 tag1 判断为 true,t2 也开始执行 tag2 生成一个新对象,这样当 t1 再次执行 tag2 时就会再生成一个新对象,这样就同时存在多个类的对象。

2.同步锁

对上面的代码稍作优化,可以看到使用了 synchronized,对判断是否需要初始化进行了同步锁,这样当线程 t1 访问时,语句被锁定,t2 运行到这里时,只能等 t1 运行完这段语句并释放之后,才能继续访问,此时 as 已经被赋予了对象,所以不会再继续新建,这样就保证了单例。

class ASingleton {

	private static ASingleton as;

	private void ASinleton() {
		System.out.print("ASingleton init!\n");
	}

	public static ASingleton getInstance() {
		synchronized (ASingleton.class) {
			if (as == null) {
				as = new ASingleton();
			}
		}
		return as;
	}
}

但是这样也存在一个问题,每个线程每次获取单例都要进入同步锁,这样累计下来必然影响效率。

3.双重检查锁定

那么在判断 as 为 null 后,对 as 的初始化进行同步锁呢?

public static ASingleton getInstance() {

		if (as == null) {
			synchronized (ASingleton.class) {
				if (as == null) {           //tag1
					as = new ASingleton();  //tag2
				}
			}
		}

		return as;
	}

这样子,当判断 as 为 null 时,才会进行初始化,同时由于初始化过程加锁,所以 t1 和 t2 无法同时访问初始化语句 tag2,也保证了只能创建一个单例。

看起来很完美,但是由于 java 语言的特性,在该段代码编译为汇编语言时,上述方法会被编译为类似下面的过程:

1.判断as是否为null
2.令as = ASingleton() //注意此时只是为as分配了内存,并未执行ASingleton的构造方法
3.开始执行ASingleton构造方法,as有了初始化的值
4.返回as

那么,当 t1 执行到语句 2,而 t2 开始执行语句 1 时,此时由于 as 已经分配了内存不为 null,所以 t2 直接执行语句 4,此时 t2 获取到的是一个没有执行构造方法的 ASingleton 对象,显然这样十分危险。在线程复杂的情况下很容易出现问题。

下面提供了两个结局思路,为简便起见,将其简单分为“饿汉模式”和“懒汉模式”(其实上述方法也可分为这两个模式,but,who cares...)。

4.饿汉模式实现单例

饿汉模式,即在声明的时候就将对象初始化。

这样实现单例的原理是类的静态变量全局唯一。

class ASingleton {

	private static ASingleton as = new ASingleton();

	private void ASinleton() {
		System.out.print("ASingleton init!\n");
	}

	public static ASingleton getInstance() {
		return as;
	}
}

但是这样仍然有个问题,可能在用到 ASingleton 类的时候,并不需要立即获取到其单例,在这种情况下,饿汉模式仍然有浪费资源的嫌疑。

5.懒汉模式实现单例

懒汉模式,只有要用到该实例时,才获取该单例。

这次我们用到的时静态内部类,静态内部类与类的静态变量不同,只有明确调用静态内部类的时候才会初始化静态内部类。


class ASingletonFactory{

	static class ASingleton {

		public static ASingleton as = new ASingleton();

		private void ASinleton() {
			System.out.print("ASingleton init!\n");
		}
	}


	public static ASingleton getInstance() {
		return ASingleton.as;
	}
}

嗯,至此已经完成了 java 实现单例的绝大部分方法,但其实还有一张更加简洁的方法,那就是用 enmu 实现。

6.enmu 实现单例

由于枚举类型的对象是唯一的,所以是实现单例的较优选择。

enum SingletonEnum{
	INSTANCE;

	public void dosth(){

	}
}

但是,android 开发者要注意,枚举占用的内存是普通单例的两倍多,所以,并不推荐在 android 中使用。

关于枚举的更详细资料,参阅(深入理解 Java 枚举类型(enum))[http://blog.csdn.net/javazejian/article/details/71333103#t7open in new window]

文章标题:《Java 创建线程安全的单例 Singleton》
本文作者: JI,XIAOYONG
发布时间: 2018/02/14 18:34:16 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/36721083.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8