スレッド同期---synchronized
public class TextThread{
/**
* @param args
*/
public static void main(String[] args) {
// TODO
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というオブジェクトであり,BのロックはObjが属するクラスである.
まとめは次のとおりです.
synchronizedがロックしているオブジェクトを明らかにすると、より安全なマルチスレッドプログラムの設計に役立ちます.
共有リソースへの同期アクセスをより安全にするためのテクニックもあります.
1.public/protectedのinstance変数を定義せずにprivateのinstance変数+そのgetメソッドを定義します.変数をpublicと定義すると、オブジェクトは外部で同期メソッドの制御を迂回して直接取得し、変更できます.これもJavaBeanの標準的な実装方法の一つです.
2.instance変数が配列やArrayListなどのオブジェクトである場合、上記の方法は依然として安全ではありません.外部オブジェクトがgetメソッドでこのinstanceオブジェクトの参照を取得した後、別のオブジェクトに指向すると、このprivate変数も変わり、危険ではありません.このときgetメソッドにsynchronized同期を加え,このprivateオブジェクトのclone()のみを返す必要がある.これにより,呼び出し側がオブジェクトコピーの参照を得る.