Stringは定数プールに格納されるため、2つの側面を考慮すると、Stringは通常同期ロックとして使用されないことがわかっています。
-
Stringをロックとして使用し、互いに影響を与えることなくさまざまな変数をロックして、Objectのようにできることを期待しています。ただし、2つのStringオブジェクトが定数プール内の同じ文字列を指している場合があり、それらのロックが相互に影響を及ぼします。例は次のとおりです。2つのクラスの場合、文字列リテラルの割り当てを使用して2つのStringオブジェクトを宣言し、
synchronized
キーワードを使用して2つのStringオブジェクトを個別にロックします。文字列定数プールのため、2つのStringオブジェクトStringオブジェクトは同じものを指します。定数プール内のメモリ領域。したがって、2つのロック方法は互いに影響します。StringSyn1とStringSyn2の2つのクラスがあり、それぞれにロックメソッドがあります。
package com.yogurt.test; import java.util.concurrent.TimeUnit; public class StringSyn1 { private String lockString = "yogurt"; public void lock() { synchronized (lockString) { System.out.println("StringSyn1 get lock"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("StringSyn1 is about to exit"); } } }
package com.yogurt.test; import java.util.concurrent.TimeUnit; public class StringSyn2 { private String lockString = "yogurt"; public void lock() { synchronized (lockString) { System.out.println("StringSyn2 get lock"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("StringSyn2 is about to exit"); } } }
別のテストクラスを作成して実行します
public static void main(String[] args) { StringSyn1 syn1 = new StringSyn1(); StringSyn2 syn2 = new StringSyn2(); (new Thread(() -> syn1.lock())).start(); (new Thread(() -> syn2.lock())).start(); }
2つのクラスのlockString変数は文字列リテラルを介して割り当てられるため、syn1とsyn2が順番に出力されることがわかります。このように、2つのクラスのlockString変数はすべて、定数プール内の同じ文字列オブジェクトを指します。クラスのlockメソッドは同じStringオブジェクトで同期されるため、syn2はsyn1によってブロックされます。**実際の開発では、ロックとして使用されているStringオブジェクトが定数プールからのものであるかどうか、および他の場所でロックとしても使用されているかどうかを知ることは困難です。**もちろん、上記のlockStringの一部を
private String lockString = new String("yogurt");
2つのクラスのlockStringに変更すると、それらは2つの異なるStringオブジェクトになり、2つのクラスのlockメソッドが相互に影響を与えず、syn2が待機しないことがわかります。 syn1が終了します。 -
文字列にビジネス上の意味がある場合、同じ内容の文字列が同じロックを追加することを期待します。たとえば、注文番号を使用してロックする場合、特定の注文番号は、一度にその注文番号に対して1つの操作しか実行できないことが望まれます。注文番号が同じである限り、ロックを追加する必要があります。注文番号が処理されているとき、同じ注文番号を持つ複数の要求がこのロックでキューに入れられる必要があります。これは私たちが期待することですが、文字列の内容は同じであり、同じ文字列オブジェクトであるという保証はありません。上記の例に示すように、2つの文字列オブジェクトが両方とも定数プールから参照されている場合、それらは同じオブジェクトであり、同じコンテンツ文字列と同じロックの効果を実現できます。しかし、2つの文字列オブジェクトの内容が同じで、2つの異なるオブジェクトである場合はどうなるでしょうか。たとえば、上記の新しいキーワードを使用して文字列を作成します。同時に2つのリクエストが来ると仮定すると、それらは同じ注文番号を持っていますが、注文番号を格納する文字列オブジェクトは別のオブジェクトです。このとき、ロックは失敗し、同じものに対して重大な操作が発生します。同時に注文番号。
一般に、文字列にビジネス上の意味がある場合にのみ、文字列をロックとして使用する必要があります。つまり、2番目のケースでは、文字列をロックとして使用する必要があります。前者の場合、通常のオブジェクトをロックとして使用できます。
では、解決策は何ですか?最も簡単な方法は、文字列定数プールの特性を使用し、Stringインターンメソッドを呼び出し、Stringオブジェクトを定数プールに入れてから、それをロックとして使用して、同じ内容の文字列変数がを指すようにすることです。同じオブジェクトアップ。
ただし、Javaのネイティブインターンは定数プールを自動的にクリーンアップせず、頻繁にGCを引き起こす可能性があります。
幸い、GoogleのguavaパッケージにはInternerがあり、GCの問題を回避するために、弱い参照に基づいて定数プールを作成するために使用できます。使用例は以下のとおりです。
package com.yogurt.test;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
public class StringSyn {
private Interner<String> stringPool = Interners.newWeakInterner();
/**
* 针对具体订单号,执行临界操作
* **/
public void doCriticalOperation(String orderId) {
synchronized (stringPool.intern(orderId)) {
//do something
}
}
}