JAvaにおけるsynchronizedの使い方
package nlc.tools.common;
public class TextThread {
/**
* @param args
*/
public static void main(String[] args) {
TxtThread tt = new TxtThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TxtThread implements Runnable {
int num = 100;
String str = new String();
public void run() {
while (true) {
synchronized (str) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.getMessage();
}
System.out.println(Thread.currentThread().getName()
+ " this is " + num--);
}
}
}
}
}
上記の例では,時間差,すなわちエラーの機会を作るためにThread.sleep(10)を用いた.
Javaはマルチスレッドのサポートと同期メカニズムが好まれており、synchronizedキーワードを使用するとマルチスレッド共有データ同期の問題を簡単に解決できるように見えます.いったいどうなのか――synchronizedキーワードの役割も深く理解しなければならない.
総じてsynchronizedキーワードは、関数の修飾子としてもよいし、関数内の文としてもよい.すなわち、通常の同期方法と同期文ブロックとしてもよい.さらに詳細な分類の場合、synchronizedはinstance変数、object reference(オブジェクト参照)、static関数、class literals(クラス名字面定数)に作用します.
さらに説明する前に、いくつかの点を明確にする必要があります.
A.synchronizedキーワードがメソッドに追加されてもオブジェクトに追加されても、コードや関数をロックとするのではなく、取得されたロックはオブジェクトであり、同期メソッドは他のスレッドのオブジェクトにアクセスされる可能性が高い.
B.オブジェクトごとにロックが1つしか関連付けられていない.
C.同期を実現するには大きなシステムオーバーヘッドを代価とし、デッドロックをもたらす可能性もあるので、無駄な同期制御をできるだけ避ける.
次にsynchronizedが異なる場所でコードに与える影響について議論する.
P 1,P 2が同じクラスの異なるオブジェクトであると仮定し,このクラスでは以下のような場合の同期ブロックや同期方法が定義されており,P 1,P 2はそれらを呼び出すことができる.
1.synchronizedを関数修飾子とする場合、サンプルコードは以下の通りです.
Public synchronized void methodAAA() {
//….
}
これが同期方法ですが、synchronizedがロックしているオブジェクトはどれですか?この同期メソッドオブジェクトを呼び出すことをロックします.すなわち、1つのオブジェクトP 1が異なるスレッドでこの同期方法を実行すると、それらの間に反発が形成され、同期の効果が得られる.しかし、このオブジェクトが属するClassによって生成された別のオブジェクトP 2は、synchronizedキーワードが付加されたメソッドを任意に呼び出すことができる.
上記のサンプルコードは、次のコードと同じです.
public void methodAAA() {
synchronized (this) {//(1)
//…..
}
}
(1)のthisは何を指していますか.このメソッドを呼び出すオブジェクト(P 1など)を指します.可視同期方法は実質的にsynchronizedをobject referenceに作用させる.――P 1オブジェクトロックを取得したスレッドは、P 1の同期方法を呼び出すことができるが、P 2にとって、P 1というロックはそれとは無関係であり、プログラムはこのような状況下で同期メカニズムの制御から脱し、データの混乱をもたらす可能性がある:(
2.ブロックを同期します.サンプルコードは次のとおりです.
public void method3(SomeObject so) {
synchronized(so) {
//…..
}
}
この場合、ロックはsoというオブジェクトであり、誰がこのロックを手に入れたら、その制御されたコードを実行することができる.明確なオブジェクトがロックとして存在する場合、このようにプログラムを書くことができるが、明確なオブジェクトがロックとして存在せず、コードを同期させたいだけである場合、ロックとして特殊なinstance変数(オブジェクトでなければならない)を作成することができる.
class Foo implements Runnable {
private byte[] lock = new byte[0];// instance
Public void methodA() {
synchronized(lock) { //… }
}
//…..
}
注意:ゼロ長のbyte配列オブジェクトを作成すると、どのオブジェクトよりも経済的になります.コンパイルされたバイトコードを表示します.ゼロ長のbyte[]オブジェクトを生成するには3つの操作コードしか必要ありません.Object lock=new Object()には7行の操作コードが必要です.
3.static関数にsynchronizedを使用します.サンプルコードは次のとおりです.
Class Foo {
public synchronized static void methodAAA() {// static
//….
}
public void methodBBB() {
synchronized(Foo.class);//class literal( )
}
}
コード中のmethodBBB()メソッドはclass literalをロックとする場合,同期したstatic関数と同様の効果が得られるが,取得したロックは特別であり,現在このメソッドを呼び出しているオブジェクトが属するクラス(Class,このClassによって生成された特定のオブジェクトではない)である.
「Effective Java」でFoo.classとP 1.getClass()を同期ロックに使用するのはまだ違い、P 1.getClass()ではこのClassをロックする目的を達成できないのを見たことがあるのを覚えています.P 1はFooクラスによって生成されたオブジェクトを指します.
クラスにsynchronizedのstatic関数Aが定義され、synchronizedのinstance関数Bが定義されている場合、クラスの同じオブジェクトObjがマルチスレッドでAとBの2つのメソッドにそれぞれアクセスすると、ロックが異なるため同期は構成されません.AメソッドのロックはObjが属するClassであり、BのロックはObjというオブジェクトです.
まとめは次のとおりです.
synchronizedがロックしているオブジェクトを明らかにすると、より安全なマルチスレッドプログラムの設計に役立ちます.
共有リソースへの同期アクセスをより安全にするためのテクニックもあります.
1.privateのinstance変数+そのgetメソッドを定義し、public/protectedのinstance変数を定義しない.変数をpublicと定義すると、オブジェクトは外部で同期メソッドの制御を迂回して直接取得し、それを変更することができる.これもJavaBeanの標準実装方式の一つである.
2.配列やArrayListなどのinstance変数がオブジェクトである場合、上記の方法は依然として安全ではありません.外部オブジェクトがgetメソッドでこのinstanceオブジェクトの参照を取得した後、別のオブジェクトを指すと、このprivate変数も変わり、危険ではありません.この場合、getメソッドにsynchronizedを加えて同期する必要があります.ああ、このprivateオブジェクトのclone()のみが返されます.これで、呼び出し側が得たのはオブジェクトコピーの参照です.