オブジェクトと変数への同時アクセス


単一のモードではsynchronizedとvolatileが含まれ、このセクションではsynchronizedとvolatileがオブジェクトと変数の同時アクセスを制御します.
可変メンバー変数修飾子volatile:
volatileは、変数のプライベートコピーを保存するのではなく、共有コピーと直接対話する必要があることをVMに伝えます.volatileは「読む」ことを強調します.現在のほとんどのプロセッサアーキテクチャでは、volatile読み取り操作のオーバーヘッドは非常に低く、非volatile読み取り操作とほぼ同じです.一方、volatile書き込み操作のオーバーヘッドは、非volatile書き込み操作よりも多く、可視性を保証するにはメモリ定義(Memory Fence)が必要であるため、それでもvolatileの総オーバーヘッドはロック取得よりも低い.
Volatile変数はsynchronizedの可視性特性を持つが,原子特性は備えていない.すなわち、スレッドはvolatile変数の最新値を自動的に発見することができる.Volatile変数はスレッドのセキュリティを提供するために使用できますが、複数の変数間または変数の現在の値と修正後の値との間に制約がない非常に限られた使用例のセットにのみ適用できます.したがって、volatileを単独で使用することは、カウンタ、反発ロック、または複数の変数に関連する不変式(Invariants)を有するクラス(例えば「start<=end」)を実現するのに十分ではない.
たとえば、volatileの機能はカウンタを実現するのに十分ではありません.++xは実際には3つの操作(読み取り、追加、記憶)の簡単な組み合わせであるため、複数のスレッドがたまたまvolatileカウンタに対してインクリメンタル操作を同時に実行しようとすると、その更新値が失われる可能性がある.volatileキーワードは、int、float、booleanなどの単純なタイプの変数を宣言するために使用されます.これらの単純なデータ型がvolatileとして宣言されると、それらの操作は原子レベルになります.
(1)volatileをステータスIDとして使用
public class  ZX extends Thread
{
	private volatile boolean zx;
	public void run(){
		while(!zx){}
	}
	public void tellMeTOStop(){
		zx=true;
	}
}  
注:zxがvolatileとして宣言されていない場合、スレッドがrunを実行するときに自分のコピーをチェックしていると、他のスレッドがtellMeToStop()を呼び出してzxの値を変更したことをタイムリーに知ることはできません.
(2)volatileを一度に安全に配布する
public class BackgroundFloobleLoader {
    public volatile Flooble theFlooble;

    public void initInBackground() {
        // do lots of stuff
        theFlooble = new Flooble();  // this is the only write to theFlooble
    }
}

public class SomeOtherClass {
    public void doWork() {
        while (true) { 
            // do some stuff...
            // use the Flooble, but only if it is ready
            if (floobleLoader.theFlooble != null) 
                doSomething(floobleLoader.theFlooble);
        }
    }
}
注:volatileは主に「読む」ことを強調していますが、ここではtheFlooble参照はvolatileタイプではありません.doWork()の中のコードは対を解除していますtheFloobleの参照では、不完全な構造が得られます.Flooble .volatileタイプのリファレンスは、オブジェクトのパブリケーション形式の可視性を確保しますが、オブジェクトのステータスがパブリケーション後に変更される場合は、追加の同期が必要です.
(3)volatileを複数の対立観察結果に用いる
public class UserManager {
    public volatile String lastUser;

    public boolean authenticate(String user, String password) {
        boolean valid = passwordIsValid(user, password);
        if (valid) {
            User u = new User();
            activeUsers.add(u);
            lastUser = user;
        }
        return valid;
    }
} 
注:このモードは前のモードの拡張である.プログラム内の他の場所で使用するために値をパブリッシュしますが、使い捨てイベントのパブリケーションとは異なり、一連の独立したイベントです.このモードでは、パブリッシュされた値が有効で可変であることが要求されます.すなわち、値のステータスはパブリッシュ後も変更されません.この値を使用するコードは、値がいつでも変化する可能性があることを明確にする必要があります.
(4)volatileをbeanモードに使用する
public class Person {
    private volatile String firstName;
    private volatile String lastName;
    private volatile int age;

    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }

    public void setFirstName(String firstName) { 
        this.firstName = firstName;
    }

    public void setLastName(String lastName) { 
        this.lastName = lastName;
    }

    public void setAge(int age) { 
        this.age = age;
    }
}
注:volatile beanモードでは、JavaBeanのすべてのデータ・メンバーがvolatileタイプであり、getterメソッドとsetterメソッドは非常に一般的である必要があります.対応するプロパティの取得または設定に加えて、論理は含まれません.また、オブジェクトが参照するデータ・メンバーの場合、参照するオブジェクトは有効で可変でなければなりません.(配列参照が宣言されるとvolatileの場合、配列自体ではなく参照のみがvolatileの意味を持つ).任意のvolatile変数に対して、不変または制約にJavaBeanプロパティを含めることはできません.
JAvaでは、volatile変数と同期ブロック(メソッド)の2つの内在的な同期メカニズムが提供されています.同期ブロック(メソッド)はsynchronizedキーワードによく使用されます.
synchronizedキーワードの役割ドメイン:
(1)同期方法:
a.オブジェクトインスタンスを同期する方法:synchronized aMethod(){}は、複数のスレッドがこのオブジェクトに同時にアクセスするsynchronized方法を防止することができる(1つのオブジェクトに複数のsynchronized方法がある場合、1つのスレッドがその中の1つのsynchronized方法にアクセスしている限り、他のスレッドはこのオブジェクトのいずれかのsynchronized方法に同時にアクセスできない).この場合,異なるオブジェクトインスタンスのsynchronized法は干渉しない.すなわち、他のスレッドは、同じクラスの別のオブジェクトインスタンスのsynchronizedメソッドに同時にアクセスすることができる.
b.クラスを同期する方法:あるクラスの範囲、synchronized static aStaticMethod{}は複数のスレッドがこのクラスのsynchronized static方法に同時にアクセスすることを防止する.クラスのすべてのオブジェクトインスタンスに役立ちます.
public class TextThread
{

 /**
  * @param args
  */
 public static void main(String[] args)
 {
  // TODO  
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
 }

}
class TxtThread implements Runnable
{
 int num = 3;
 String str = new String();
 public void run()
 {
  while (true)
  {
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(2);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
  }
 }
}

(2)同期ブロック:synchronizedキーワードは、このブロックのリソースのみに反発アクセスを行うことを示すメソッド内のブロックにも使用することができる.使用法はsynchronized(this){/*ブロック*/}で、その役割ドメインは現在のオブジェクトです. 
注意:synchronizedキーワードは継承できません.つまり、ベースクラスのメソッドsynchronized f(){}は、継承クラスでは自動的にsynchronized f(){}ではなく、f(){}になります.クラスを継承するには、synchronizedメソッドとして明示的に指定する必要があります.
総じてsynchronizedキーワードは、関数の修飾子としてもよいし、関数内の文としてもよい.すなわち、通常の同期方法と同期文ブロックとしてもよい.さらに詳細な分類の場合、synchronizedはinstance変数、object reference(オブジェクト参照)、static関数、class literals(クラス名字面定数)に作用します.
volatileとsynchronizedの違い:
1.volatileの本質は、jvmの現在の変数がレジスタ(ワークメモリ)の値が不確定であることを伝え、ホストメモリから読み出す必要があることである.synchronizedは現在の変数をロックし、現在のスレッドのみがこの変数にアクセスでき、他のスレッドがブロックされます.2.volatileは変数レベルでのみ使用できます.synchronizedは、変数、メソッド、クラスレベルで使用できます.3.volatileは変数の修正可視性しか実現できず、原子性を保証できる.synchronizedは変数の修正可視性と原子性を保証することができる.4.volatileはスレッドのブロックをもたらさない.synchronizedはスレッドのブロックを引き起こす可能性があります.
5.volatileタグの変数はコンパイラによって最適化されません.synchronizedタグの変数はコンパイラによって最適化できます.
volatileとsynchronizedを組み合わせた「低コストの読み書きロック」
public class CheesyCounter {
  private volatile int value; private int value1;                public int getvalue1() {return value1;}     public int getValue() { return value; }
    public synchronized int increment() {
        return value++;
    }
}

現在のスレッドに格納されているvalue 1の数値を取得します.複数のスレッドには複数のvalue 1変数コピーがあり、これらのvalue 1間は互いに異なることができます.すなわち、別のスレッドがスレッド内のvalue 1値を変更した可能性がありますが、この値は現在のスレッドのvalue 1値と異なる場合があります.実際、Javaには「メイン」メモリ領域という考えがあり、変数の現在の「正確な値」が格納されています.各スレッドには独自の変数コピーがありますが、この変数コピー値はプライマリメモリ領域に格納されているものとは異なります.したがって、「プライマリ」メモリ領域のvalue 1値は1であり、スレッド1のvalue 1値は2であり、スレッド2のvalue 1値は3である.これは、スレッド1とスレッド2がそれぞれのvalue 1値を変更し、この変更がプライマリメモリ領域または他のスレッドに伝達されない場合に発生する.
getiValue()は、「プライマリ」メモリ領域のi 2数値を取得します.volatileで修飾された変数には、プライマリメモリ領域とは異なる変数コピーは許可されません.すなわち、1つの変数がvolatileで修飾された後、すべてのスレッドで同期されなければならない.任意のスレッドで値が変更されると、他のすべてのスレッドはすぐに同じ値を取得します.当然、volatile修飾変数アクセスは、スレッドが独自の変数コピーを持っているため、一般的な変数よりも多くのリソースを消費します.
参照先:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html