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

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