线上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()
,这个赋值过程,会被拆分为如下几个步骤:
其实在单线程环境下,JMM会打断顺序,并且不会对最终结果有任何影响;比如:
但是在多线程环境下,这种reorder就会带来问题,即第一个线程运行到第二次check lock的时候,在赋值的第二步,将还没有完成初始化的新对象直接assign给变量,就被切换到另外一个线程了,这时第二个线程直接将这个未完成初始化的对象直接返回,这就造成了问题…
这个问题知道JDK1.5才解决, 请看JSR133