線上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