前几天去某云面试,面试官问到单例模式,自然老生常谈的,最后面试官问我,有没有了解过多线程模式下的单例模式?我就懵了,接触最多的PHP和JS都是单线程的,之前也没考虑过线程安全的问题,回来查了查资料,特总结如下:(PS:下文java代码来自安卓同事讲解)
1.线程安全
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2.线程不安全的单例
不安全的单例就是静态单例在多线程同时首次引用此类时,可能创建多个实例,我们平时写的最多的单例模式是懒汉式单例,即只有初次执行getInstance()时,才创建单例。例如以下代码:
var H5SdkLog = (() => {
function _module() {
this.instance_id = Math.floor(Math.random()*100);
}
var _instance = null;
return {
getInstance:() => {
if(!_instance){
_instance = new _module();
}
return _instance;
}
}
})();
class Utils{
private static $instance;
//构造方法私有化,防止外部创建实例
private function __construct(){}
public static function getInstance()
{
if(!(self::$instance instanceof self)){
self::$instance = new self();
}
return self::$instance;
}
//克隆方法私有化,防止复制实例
private function __clone(){}
}
public class SdkAppManagerUtils {
private volatile static SdkAppManagerUtils instance;
public static SdkAppManagerUtils getInstance() {
if (instance == null) {
instance = new SdkAppManagerUtils();
}
return instance;
}
}
通常来讲,PHP和JS都是单线程的,使用懒汉式单例没有啥,毕竟未必用得到这个类,推迟到使用时候再生成单例的思路是正确的,符合编程思想的。但对于java这种支持多线程的语言,就存在线程不安全,比如两个线程同时请求获取单例,这种情况下,首先抢占到CPU的线程会在堆上建立一个实例,静态区的instance指向堆中的该实例,而后续抢占到CPU的线程都会在堆上建立一个新的实例,覆盖掉之前的引用地址。所以需要特殊处理。
3.线程安全的单例-饿汉式/同步锁
//饿汉式
public class SdkAppManagerUtils {
private volatile static SdkAppManagerUtils instance = new SdkAppManagerUtils();
public static SdkAppManagerUtils getAppManager() {
return instance;
}
}
//同步锁
public class SdkAppManagerUtils {
private volatile static SdkAppManagerUtils instance;
public static SdkAppManagerUtils getAppManager() {
if (instance == null) {
synchronized (SdkAppManagerUtils.class) {
if (instance == null) {
instance = new SdkAppManagerUtils();
}
}
}
return instance;
}
}