Java单例模式double check locking在JDK1.5之前的问题

线上datadog最近总是在报警网站在登录时颁发证书操作耗时太长,即spring security oauth2 endpoint /oauth/token这个API performance慢.

在阅读源码的时候看到了个有趣的地方,如下:

# org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory
public KeyPair getKeyPair(String alias, char[] password) {
	try {
		synchronized (lock) {
			if (store == null) {
				synchronized (lock) {
					store = KeyStore.getInstance("jks");
					store.load(resource.getInputStream(), this.password);
				}
			}
		}
		RSAPrivateCrtKey key = (RSAPrivateCrtKey) store.getKey(alias, password);
		RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
		PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
		return new KeyPair(publicKey, key);
	}
	catch (Exception e) {
		throw new IllegalStateException("Cannot load keys from store: " + resource, e);
	}
}

这段代码只要目的是对JWT token进行加密,并且这里使用的是非对称加密.

其中这段代码的上半段的两次synchronized引起了我的注意,这不是单例模式中的DCL(double check lock)嘛 😄

大部分人肯定都会写,并且也知道为什么要使用DCL,可是都知道DCL还有一段小历史吗? DCL其实在JDK1.5之前是有问题的,之道JDK1.5才修复完毕.

class DCLSingleton {
    private static voaltile DCLSingleton _instance  = null;
    private DCLSingleton() {}
    public static DCLSingleton instance() {
        if (_instance == null) { // 1st check
            synchronized (DCLSingleton.class) {
                if (_instance == null){ // 2nd check
                    _instance = new DCLSingleton();
                }
            }
        }
        return _instance;
    }
}

上面这段标准的DCL使用了关键字volatile,也正是这个关键字,才引入了DCL的问题.

在JDK1.5之前volatile变量的赋值过程,是有可能被reorder的.

JMM或者说Java编译器,会根据自己的判断,在当前线程不影响最终结果的时候,对语句进行重排,这点大家都应该之道; 其中JDK1.5之前的volatile变量的赋值,也会被重排…

比如上面代码中的_instance=new DCLSingleton(),这个赋值过程,会被拆分为如下几个步骤:

  • 新建对象
  • 为新对象赋初值
  • 将新对象assgin给变量

其实在单线程环境下,JMM会打断顺序,并且不会对最终结果有任何影响;比如:

  • 新建对象
  • 将新对象assgin给变量
  • 为新对象赋初值

但是在多线程环境下,这种reorder就会带来问题,即第一个线程运行到第二次check lock的时候,在赋值的第二步,将还没有完成初始化的新对象直接assign给变量,就被切换到另外一个线程了,这时第二个线程直接将这个未完成初始化的对象直接返回,这就造成了问题…

这个问题知道JDK1.5才解决, 请看JSR133

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章