JAva-マルチスレッドが同じオブジェクトを同時に操作する解決方法:ReadWriteLockの使用
7795 ワード
説明すると、こちらの文章は比較的簡単に書かれており、ReadWriteLockの使用にのみ関連しており、具体的なソースコード実現原理には関連していない.
1、シーンを使う:
まずこちらは実際に開発で使用しましたが、開発環境は、データセンターが1つある(一応1つのMapの集合として)、2つのサブスレッドA,Bがあり、そのうちAスレッドは5秒ごとに他の場所から新しいデータを取得してデータセンターの中のデータと融合し、Bスレッドは5分おきにデータセンターからMapの集合を取り出して解析し、最初はテストは問題ありませんが、テストが多くなると一定の確率で発生することがわかります「java.util.C o n c u r r e ntModificationException」異常、つまり同じオブジェクトが同時に2つのスレッドで操作されるのは、Bスレッドがデータを取り出して解析データを遍歴すると、AスレッドがデータセンターのMapセットを操作し、最終的に参照されたペアが1つであるため、上記の問題が発生したためです.
最初に思いついたのは、プロトタイプを使って、Mapのクローンを行い(主にクローンを使うことを考慮すると犠牲メモリですが、ロックを使うと犠牲になるのは性能です)、Mapのデフォルトは浅いクローンです(HashMapを使用していますが、Mapはインタフェースがクローン方法がないので注意してください)、単独でクローンしたところMapの中のオブジェクトが参照の1つであることがわかりましたので、中のオブジェクトも単独でクローンする必要があります(Mapのデフォルトクローンは自分のオブジェクトのみをクローン化していますが、mapの中にオブジェクトがある場合はクローン化しません)、次にclone書き換えの方法を貼ります.私は直接書き換えたデータセンターのclone方法です.中にMap集合があるからです.
そしてBスレッドがMapを取得してから操作するのは別のオブジェクトだと思いますが、A側はいつ操作しても影響しないでしょう...
その結果、テストでは、上記の異常問題が発生する確率が一定であることが分かった.Bスレッドがmapコレクションを取得したとき、このデータのMapコレクションがクローン化されたときにAスレッドが操作に来たということです.この点で異常が発生しました.個人的に考えてみると、やはりロックをかけなければならないような気がしますが、直接ロックをかけて、クローンを消しました.
2リードライトロックReadWriteLockの使用
それから本開発環境に適したロックの使用を探して、ReadWriteLockというのはちょうど適していることを発見して、読み書きロック:つまり読み書きと読み書きが同時にできて、読み書きが反発して、書いて反発して、この時私たちは読む時に読み書きロックを使って、書く時に書くロックを使うことができます.
まず、読み取りと読み取りを検証します.
ログを見てください.
図から2つのリードスレッドが同時に行われていることがわかります.次に、2つの書き込みロックが同時に実行されたらどうなるかを見てみましょう.
図から分かるように、あるスレッドが先に書き終わってから書き込みロックを解除してから別のスレッドが始まります.注意:読み取りと書き込みの同時操作もそうです.
具体的に私のプロジェクトの中でどのように使うのはここで貼らないで、私がこれを読んだ後に分析の操作を使うため、上のcloneも使ったので、ただ読む操作の中で直接クローンを作って帰っただけです.
1、シーンを使う:
まずこちらは実際に開発で使用しましたが、開発環境は、データセンターが1つある(一応1つのMapの集合として)、2つのサブスレッドA,Bがあり、そのうちAスレッドは5秒ごとに他の場所から新しいデータを取得してデータセンターの中のデータと融合し、Bスレッドは5分おきにデータセンターからMapの集合を取り出して解析し、最初はテストは問題ありませんが、テストが多くなると一定の確率で発生することがわかります「java.util.C o n c u r r e ntModificationException」異常、つまり同じオブジェクトが同時に2つのスレッドで操作されるのは、Bスレッドがデータを取り出して解析データを遍歴すると、AスレッドがデータセンターのMapセットを操作し、最終的に参照されたペアが1つであるため、上記の問題が発生したためです.
最初に思いついたのは、プロトタイプを使って、Mapのクローンを行い(主にクローンを使うことを考慮すると犠牲メモリですが、ロックを使うと犠牲になるのは性能です)、Mapのデフォルトは浅いクローンです(HashMapを使用していますが、Mapはインタフェースがクローン方法がないので注意してください)、単独でクローンしたところMapの中のオブジェクトが参照の1つであることがわかりましたので、中のオブジェクトも単独でクローンする必要があります(Mapのデフォルトクローンは自分のオブジェクトのみをクローン化していますが、mapの中にオブジェクトがある場合はクローン化しません)、次にclone書き換えの方法を貼ります.私は直接書き換えたデータセンターのclone方法です.中にMap集合があるからです.
# NetWorkDataBean : , Map this.udpMap、 this.tcpMap 、this.mediaMap
@Override
public Object clone() throws CloneNotSupportedException {
NetWorkDataBean bean = (NetWorkDataBean) super.clone();
if (null == this.udpMap) {
this.udpMap = new HashMap<>();
} else {
bean.udpMap = new HashMap<>();
for (Map.Entry> entry : this.udpMap.entrySet()) {
Map mediaMapOld = entry.getValue();
Long mediaKey = entry.getKey();
Map mediaMapNew = new HashMap<>();
if (mediaMapOld != null && mediaMapOld.size() > 0) {
for (Map.Entry dataBeanEntry : mediaMapOld.entrySet()) {
UDPNetworkAnalyBean dataBean = dataBeanEntry.getValue();
String key = dataBeanEntry.getKey();
mediaMapNew.put(key, (UDPNetworkAnalyBean) dataBean.clone());
}
}
bean.udpMap.put(mediaKey, mediaMapNew);
}
}
if (null == this.tcpMap) {
this.tcpMap = new HashMap<>();
} else {
bean.tcpMap = new HashMap<>();
for (Map.Entry> entry : this.tcpMap.entrySet()) {
Map mediaMapOld = entry.getValue();
Long mediaKey = entry.getKey();
Map mediaMapNew = new HashMap<>();
if (mediaMapOld != null && mediaMapOld.size() > 0) {
for (Map.Entry dataBeanEntry : mediaMapOld.entrySet()) {
NetworkAnalyBean.DataBean dataBean = dataBeanEntry.getValue();
String key = dataBeanEntry.getKey();
mediaMapNew.put(key, (NetworkAnalyBean.DataBean) dataBean.clone());
}
}
bean.tcpMap.put(mediaKey, mediaMapNew);
}
}
if (null == this.mediaMap) {
bean.mediaMap = new HashMap<>();
} else {
bean.mediaMap = new HashMap<>();
for (Map.Entry entry : this.mediaMap.entrySet()) {
bean.mediaMap.put(entry.getKey(), entry.getValue());
}
}
return bean;
}
そしてBスレッドがMapを取得してから操作するのは別のオブジェクトだと思いますが、A側はいつ操作しても影響しないでしょう...
その結果、テストでは、上記の異常問題が発生する確率が一定であることが分かった.Bスレッドがmapコレクションを取得したとき、このデータのMapコレクションがクローン化されたときにAスレッドが操作に来たということです.この点で異常が発生しました.個人的に考えてみると、やはりロックをかけなければならないような気がしますが、直接ロックをかけて、クローンを消しました.
2リードライトロックReadWriteLockの使用
それから本開発環境に適したロックの使用を探して、ReadWriteLockというのはちょうど適していることを発見して、読み書きロック:つまり読み書きと読み書きが同時にできて、読み書きが反発して、書いて反発して、この時私たちは読む時に読み書きロックを使って、書く時に書くロックを使うことができます.
まず、読み取りと読み取りを検証します.
ReadWriteLock perodicRwLock = new ReentrantReadWriteLock();
public void get(Thread thread) {
perodicRwLock.readLock().lock();
try{
System.out.println("start time:"+System.currentTimeMillis());
for(int i=0; i<5; i++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ": ……");
}
System.out.println(thread.getName() + ": !");
System.out.println("end time:"+System.currentTimeMillis());
}finally{
perodicRwLock.readLock().unlock();
}
}
@Override
public void onClick(View v) {
if ( v.getId( ) == R.id.request1 ) {
new Thread( new Runnable( ) {
@Override
public void run() {
get( Thread.currentThread( ) );
}
} ).start( );
new Thread( new Runnable( ) {
@Override
public void run() {
get( Thread.currentThread( ) );
}
} ).start( );
}
}
ログを見てください.
08-10 14:42:29.056 30623-30894/ I/System.out: start time:1533883349068
08-10 14:42:29.056 30623-30895/ I/System.out: start time:1533883349068
08-10 14:42:29.076 30623-30894/ I/System.out: Thread-158: ……
08-10 14:42:29.076 30623-30895/ I/System.out: Thread-159: ……
08-10 14:42:29.096 30623-30894/ I/System.out: Thread-158: ……
08-10 14:42:29.096 30623-30895/ I/System.out: Thread-159: ……
08-10 14:42:29.116 30623-30894/ I/System.out: Thread-158: ……
08-10 14:42:29.116 30623-30895/ I/System.out: Thread-159: ……
08-10 14:42:29.136 30623-30894/ I/System.out: Thread-158: ……
08-10 14:42:29.136 30623-30895/ I/System.out: Thread-159: ……
08-10 14:42:29.156 30623-30894/ I/System.out: Thread-158: ……
08-10 14:42:29.156 30623-30894/ I/System.out: Thread-158: !
08-10 14:42:29.156 30623-30894/ I/System.out: end time:1533883349169
08-10 14:42:29.156 30623-30895/ I/System.out: Thread-159: ……
08-10 14:42:29.156 30623-30895/ I/System.out: Thread-159: !
08-10 14:42:29.156 30623-30895/ I/System.out: end time:1533883349169
図から2つのリードスレッドが同時に行われていることがわかります.次に、2つの書き込みロックが同時に実行されたらどうなるかを見てみましょう.
08-10 14:47:28.826 31870-31976/ I/System.out: start time:1533883648836
08-10 14:47:28.846 31870-31976/ I/System.out: Thread-144: ……
08-10 14:47:28.866 31870-31976/ I/System.out: Thread-144: ……
08-10 14:47:28.886 31870-31976/ I/System.out: Thread-144: ……
08-10 14:47:28.906 31870-31976/ I/System.out: Thread-144: ……
08-10 14:47:28.926 31870-31976/ I/System.out: Thread-144: ……
08-10 14:47:28.926 31870-31976/ I/System.out: Thread-144: !
08-10 14:47:28.926 31870-31976/ I/System.out: end time:1533883648938
08-10 14:47:28.926 31870-31977/ I/System.out: start time:1533883648939
08-10 14:47:28.946 31870-31977/ I/System.out: Thread-145: ……
08-10 14:47:28.966 31870-31977/ I/System.out: Thread-145: ……
08-10 14:47:28.986 31870-31977/ I/System.out: Thread-145: ……
08-10 14:47:29.006 31870-31977/ I/System.out: Thread-145: ……
08-10 14:47:29.026 31870-31977/ I/System.out: Thread-145: ……
08-10 14:47:29.026 31870-31977/ I/System.out: Thread-145: !
08-10 14:47:29.026 31870-31977/ I/System.out: end time:1533883649041
図から分かるように、あるスレッドが先に書き終わってから書き込みロックを解除してから別のスレッドが始まります.注意:読み取りと書き込みの同時操作もそうです.
具体的に私のプロジェクトの中でどのように使うのはここで貼らないで、私がこれを読んだ後に分析の操作を使うため、上のcloneも使ったので、ただ読む操作の中で直接クローンを作って帰っただけです.