同時プログラミングの基礎4--同期コードブロック、同期方法、lock、デッドロック


よくある質問ですが、bankの例では、同じカードで同時にお金を取り、2つを同時に取り、それぞれ800を取りますが、カードには1000しかありません.2人で同時に操作することはできません.ここではスレッド同期が必要である.例えば次のDEMO.
まず、アカウントクラスを定義します.
package org.credo.thread.tongbu;

public class Account {
	private String account;
	private double money;
	
	public Account(String account,double money){
		this.account=account;
		this.money=money;
	}
	
	// account hashcode equals .
	public int hasCode(){
		return account.hashCode();
	}
	public boolean equals(Object obj){
		if(this==obj){
			return true;
		}
		if(obj!=null && obj.getClass()==Account.class){
			Account target=(Account)obj;
			return target.getAccount().equals(account);
		}
		return false;
	}
	
	public String getAccount() {
		return account;
	}
	public void setAccount(String account) {
		this.account = account;
	}
	public double getMoney() {
		return money;
	}
	public void setMoney(double money) {
		this.money = money;
	}
}
はビジネスクラスです.
package org.credo.thread.tongbu;

public class Draw extends Thread{
	
	private Account account;
	private double hopeGetMoney;
	
	public Draw(String name,Account account,double hopeGetMoney){
		super(name);
		this.account=account;
		this.hopeGetMoney=hopeGetMoney;
	}
	
	// , .
	public void run(){
		if(account.getMoney() >= hopeGetMoney){
			System.out.println(account.getAccount()+"---"+getName()+" , :"+hopeGetMoney);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			// 
			account.setMoney(account.getMoney()-hopeGetMoney);
			System.out.println(" :"+account.getMoney());
		}else{
			System.out.println(" , !");
		}
	}
}
テストクラス:
package org.credo.thread.tongbu;

public class NoSynchronizedTest {

	public static void main(String[] args) {
		//create a new account
		Account account=new Account(" ", 1000);
		new Draw(" A", account, 800).start();
		new Draw(" B", account, 800).start();
	}
}
結果:
インド人---インド人Bはお金を引き出すことに成功して、取り出す金額は:800.0インド人---インド人Aはお金を取ることに成功して、取り出す金額は:800.0残高は:200.0残高は:-600.0
もちろん間違っています.結局これはクレジットカードではありません.
このようなシーンでは、accountというアカウントは必然的に一定時間内に一人でしか操作できないので、スレッドの同期が必要です.

1.同期コードブロック


ビジネスクラスのrunメソッドにsynchronizedコードブロックを合計する.
public void run(){
		// account , account .
		// , 
		// ,   --- ---     .
		synchronized (account) {
			if(account.getMoney() >= hopeGetMoney){
				System.out.println(account.getAccount()+"---"+getName()+" , :"+hopeGetMoney);
				try {
					Thread.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
				// 
				account.setMoney(account.getMoney()-hopeGetMoney);
				System.out.println(" :"+account.getMoney());
			}else{
				System.out.println(" , !");
			}
		}
		// , .
	}
インド人---インド人Aはお金を引き出すことに成功して、取り出す金額は:800.0です
残高:200.0
残高が足りなくて、引き出しに失敗しました.
問題を解決するためにjavaの処理方法には「同期モニタ」が現れ、同期モニタを使用する方法は上の同期コードブロックである.文法は、同じです.
synchronized(obj){//ここでコードは同期コードブロックである}

カッコ内のobjは同期モニタであり、スレッドが同期コードを実行する前に、同期モニタに対するロックを取得しなければならないという意味である.同期コードブロックが完了するとロックが解除する.Javaでは、同期モニタとして任意のオブジェクトを使用できますが、実際には、同時アクセス可能な寄与リソースを使用して同期モニタとして使用する必要があります。上記のコードのaccountのように.


2.同期方法


同期コードブロックに対応するjavaのマルチスレッドのセキュリティサポートは、synchronizedキーワードを使用してある方法を修飾する同期方法も提供する.同期方法の場合、その同期モニタはthisであり、そのオブジェクト自体である.
同期メソッドを使用すると、スレッドセキュリティのクラスを容易に実現できます.スレッドセキュリティのクラスには、次のような特徴があります.
  • クラスのオブジェクトは、複数のスレッドによって安全にアクセスすることができる
  • .
  • スレッド毎にオブジェクトを呼び出す方法はいずれも正しい結果を得る.
  • スレッド毎にオブジェクトの任意のメソッドが呼び出された後も、オブジェクトの状態は依然として合理的な状態を保つ.

  • synchronizedキーワードは、メソッド、コードブロックを修飾することができるが、コンストラクタ、属性などを変更することはできない.
    Accountクラスに次のコードを追加します.
    	public synchronized void process(double hopeGetMoney){
    		if(money >= hopeGetMoney){
    			System.out.println(Thread.currentThread().getName()+" , :"+hopeGetMoney);
    			try {
    				Thread.sleep(1);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			// 
    			this.money=money-hopeGetMoney;
    			System.out.println(" :"+money);
    		}else{
    			System.out.println(" , !");
    		}
    	}

    ビジネスクラスのrun()を変更する方法は、次のとおりです.
    	public void run(){
    		account.process(hopeGetMoney);
    	}
    インド人Aはお金を引き出すことに成功して、取り出す金額は:800.0です
    残高:200.0
    残高が足りなくて、引き出しに失敗しました.
    可変クラスのスレッドセキュリティは、プログラムの実行効率を低下させることを代価とする.従って、性能を厳密に考慮すると、単一スレッド環境とマルチスレッド環境とを提供し、2つのバージョンを提供することができる、1つはスレッドが安全ではなく、1つのスレッドが安全で、それぞれ単一スレッドとマルチスレッドで使用する.
    例えばStringBufferはマルチスレッドで安全であるが、性能が悪い.StringBuilderは良い性能を持っていますが、スレッドは安全ではありません.

    3.同期モニタのロックを解除する


    解除:
  • 1.現在のスレッドの同期方法では、同期コードブロックの正常な動作が終了する.
  • 2.現在のスレッドは同期コードブロック、同期方法でbreak、returnなどに遭遇し、
  • 3に異常、エラーが発生する.
  • 4.プログラムは同期モニタオブジェクトのwait()メソッドを実行し、スレッドは同期モニタを一時停止して解放する.

  • 解放しない:
  • 1.期限内に実行した.sleep,thread.方法離さない
  • 2.suspendを使って掛けて、もちろんsuspendとresumeの方法を使うべきではありません.

  • JAVA 5から始まります。ロックがある


    先にコードを貼って、前章の最後のコード、変わらないで、accountコードだけを修正して以下の通りです:
    public class Account {
    	private String account;
    	private double money;
    	private final ReentrantLock lock=new ReentrantLock();
    	
    	public Account(String account,double money){
    		this.account=account;
    		this.money=money;
    	}
    	
    	public void process(double hopeGetMoney){
    		try {
    			lock.lock();
    		if(money >= hopeGetMoney){
    			System.out.println(Thread.currentThread().getName()+" , :"+hopeGetMoney);
    			try {
    				Thread.sleep(1);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			// 
    			this.money=money-hopeGetMoney;
    			System.out.println(" :"+money);
    		}else{
    			System.out.println(" , !");
    		}
    		} finally {
    			lock.unlock();
    		}
    	}
    
    	
    	// account hashcode equals .

    lockを使用しています.出力:
    インド人Aはお金を引き出すことに成功して、取り出す金額は:800.0残高は:200.0残高が不足して、お金を引き出すことに失敗します!
    ロックは、複数のスレッドが共有リソースにアクセスすることを制御ツールである.通常、ロックは共有リソースへの独占アクセスを提供する、毎回1つのスレッドだけがlockオブジェクトにロックをかけることができ、スレッドが共有リソースへのアクセスを開始する前にロックオブジェクトを得るべきである.
    シンクロロックには理解すべきこともたくさんありますが、ここは・・・ニマはもうすぐ正月だから、本当にやる気持ちがない.

    ===========デッドロック


    デッドロックとは、2つのスレッドが互いに同期モニタを解放するのを待っている間にデッドロックが発生することである.Java仮想マシンは検出していないし、デッドロックを処理するための措置も取っていないので、マルチスレッドプログラミングではこの状況に注意しなければならない.デッドロックが発生した場合、プログラム全体にエラーはなく異常はなく、ヒントはなく、スレッドがブロックされていると言っているだけで、続行できません.