JAvaスレッド間通信の一般的な解釈とコード例

8250 ワード

スレッド間通信:マルチスレッドはアドレス空間とデータ空間を共有するため、複数のスレッド間の通信は、オペレーティングシステム(すなわちカーネルのスケジューリング)を通過することなく、1つのスレッドのデータを他のスレッドに直接提供して使用することができる.
プロセス間の通信は異なり、そのデータ空間の独立性は通信が比較的複雑であることを決定し、オペレーティングシステムを通過する必要がある.進路間の通信は単機版のみであり、現在オペレーティングシステムはソケットベースのプロセス間の通信メカニズムを継承している.このようにプロセス間の通信は単台のコンピュータに限らず、ネットワーク通信を実現している.スレッド通信は主に以下のいくつかの部分に分けられ、生活の中で図書館が本を借りる例を通じて以下のように簡単に説明する.
共有オブジェクトによる通信
図書館に入ってただ1冊の《java同時プログラミング実戦》に参加して、Aさんは朝の時にこの本を借りて行って、それから午後Bさんは図書館に行ってこの本を探しに行って、この時AさんとBさんは2つのスレッドで、「java同時プログラミング実戦」は共有対象(マルチスレッドのグローバル変数に似たリソース)であり、Bさんはこの本が借りられたことに気づいたので、帰って数日待っていたが、数日後、Bさんは図書館に行ってこの本が返されたことを発見し、本を借りた.これは共有対象を通じて通信することだ.
待ち遠しい
もうすぐBAT実習生が募集されるので、Bさんはこの本を読みたいと思っています.だから、Bさんは1時間おきにこの本が返されているかどうかを見に行きます.そうすれば、プロセッサのリソースがかかりますが、本が返されたら、Bさんはすぐに知ることができます.
wait() notify() notifyAll()
図書館は寮を隔てて近いので、Bさんは1時間おきに図書館に行くのは体が弱いことに気づいたが、すぐに学校の図書館システムにメール注意機能(notify()が追加されたので、Bさんは寝ながらメールを待つことができる.
失われたシグナル
図書館システムは、本が返されると、待機者にメールを送るように設計されていますが、メールは一度しか送信できません.待機者がいなければ、メールも送る(ただしこの時点で受信者がいない)という問題が発生しました.メールは一度しか送らないので、本が返されると、本を借りるのを待つ人はいません.彼は空のメールを送りますが、その後、この本を待っている学生がいてもメールを受け取ることはありません.これらの学生は絶え間なく待っています.この問題を解決するために、私たちは待機状に入ります.図書館のおばさんに待ち続ける必要があるかどうか、電話で聞いてみましょう.
ぎじよび
図書館システムにはバグがあり、ユーザーに間違ったメールを送るかどうか、私たちは言うことを聞いて、メールを受け取ると図書館に本を借りに行きますが、図書館に着いた後、本が返されていないことに気づいて、それから他のことをします.
スレッド間の通信方式
#ロックメカニズム:反発ロック、条件変数、読み書きロックを含む
*反発ロックは、データ構造の同時修正を排他的に防止する方法を提供します.
*読み書きロックでは、複数のスレッドが共有データを同時に読み上げることができ、書き込み操作は反発します.
*条件変数は、特定の条件が真になるまでプロセスを原子的にブロックすることができます.条件のテストは,反発ロックの保護の下で行った.条件変数は常に反発ロックとともに使用されます.
#信号量メカニズム(Semaphore):無名スレッド信号量と命名スレッド信号量を含む
#信号メカニズム(Signal):類似プロセス間の信号処理
スレッド間の通信の目的は主にスレッド同期に用いられるため,スレッドはプロセス通信におけるデータ交換のための通信メカニズムのようなものではない.
スレッド通信の理解はまだ十分ではないと思います.次はいくつかのコードを共有し、理解を助けます.
①同期
ここでいう同期とは、synchronizedキーワードのように複数のスレッドがスレッド間の通信を実現することを意味する.
参考例:

public class MyObject {
	synchronized public void methodA() {
		//do something....
	}
	synchronized public void methodB() {
		//do some other thing
	}
}
public class ThreadA extends Thread {
	private MyObject object;
	//      
	@Override
	 public void run() {
		super.run();
		object.methodA();
	}
}
public class ThreadB extends Thread {
	private MyObject object;
	//      
	@Override
	 public void run() {
		super.run();
		object.methodB();
	}
}
public class Run {
	public static void main(String[] args) {
		MyObject object = new MyObject();
		//  A   B          :object
		ThreadA a = new ThreadA(object);
		ThreadB b = new ThreadB(object);
		a.start();
		b.start();
	}
}

スレッドAとスレッドBは同じMyObjectクラスのオブジェクトobjectを持っているため、この2つのスレッドは異なるメソッドを呼び出す必要があるが、同期して実行される.たとえば、スレッドBはスレッドAがmethodA()メソッドを実行した後、methodB()メソッドを実行するのを待つ必要がある.これにより、スレッドAとスレッドBとの通信が実現される.
この方式は本質的に「共有メモリ」式の通信である.複数のスレッドは、同じ共有変数にアクセスする必要があります.ロック(アクセス権を取得)を取得した人は、実行できます.
②whileポーリング方式
コードは次のとおりです.

import java.util.ArrayList;
import java.util.List;
public class MyList {
	private List list = new ArrayList();
	public void add() {
		list.add("elements");
	}
	public int size() {
		return list.size();
	}
}
import mylist.MyList;
public class ThreadA extends Thread {
	private MyList list;
	public ThreadA(MyList list) {
		super();
		this.list = list;
	}
	@Override
	 public void run() {
		try {
			for (int i = 0; i < 10; i++) {
				list.add();
				System.out.println("   " + (i + 1) + "   ");
				Thread.sleep(1000);
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
import mylist.MyList;
public class ThreadB extends Thread {
	private MyList list;
	public ThreadB(MyList list) {
		super();
		this.list = list;
	}
	@Override
	 public void run() {
		try {
			while (true) {
				if (list.size() == 5) {
					System.out.println("==5,   b     ");
					throw new InterruptedException();
				}
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;
public class Test {
	public static void main(String[] args) {
		MyList service = new MyList();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();
		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
	}
}

このように、スレッドAは条件を変更し続け、スレッドThreadBはwhile文によってこの条件(list.size()=5)が成立するか否かを検出し続け、スレッド間の通信を実現する.しかし、この方式はCPUリソースを浪費する.資源を浪費しているのは、JVMスケジューラがスレッドBにCPUを渡して実行する際、「役に立つ」仕事をしていないためで、ある条件が成立しているかどうかを絶えずテストしているからです.現実の生活のように、ある人はずっと携帯電話の画面に電話が来たかどうかを見ていたが、別のことをしているのではなく、電話が来たとき、ベルが鳴って電話が来たことを知らせた.スレッドのポーリングの影響について
③wait/notifyメカニズム
コードは次のとおりです.

import java.util.ArrayList;
import java.util.List;
public class MyList {
	private static List list = new ArrayList();
	public static void add() {
		list.add("anyString");
	}
	public static int size() {
		return list.size();
	}
}
public class ThreadA extends Thread {
	private Object lock;
	public ThreadA(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	 public void run() {
		try {
			synchronized (lock) {
				if (MyList.size() != 5) {
					System.out.println("wait begin "
					    + System.currentTimeMillis());
					lock.wait();
					System.out.println("wait end "
					    + System.currentTimeMillis());
				}
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class ThreadB extends Thread {
	private Object lock;
	public ThreadB(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	 public void run() {
		try {
			synchronized (lock) {
				for (int i = 0; i < 10; i++) {
					MyList.add();
					if (MyList.size() == 5) {
						lock.notify();
						System.out.println("       ");
					}
					System.out.println("   " + (i + 1) + "   !");
					Thread.sleep(1000);
				}
			}
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class Run {
	public static void main(String[] args) {
		try {
			Object lock = new Object();
			ThreadA a = new ThreadA(lock);
			a.start();
			Thread.sleep(50);
			ThreadB b = new ThreadB(lock);
			b.start();
		}
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

スレッドAは、ある条件が満たされるのを待つ(list.size()==5).スレッドBはリストに要素を追加し、リストのsizeを変更する.
A,B間はどのように通信していますか.つまり、スレッドAはlistをどのように知っているのか.size()はもう5ですか?
ここではObjectクラスのwait()とnotify()メソッドを用いた.
条件が満たされていない場合(list.size()!=5)、スレッドAはwait()を呼び出してCPUを破棄し、ブロック状態に入る.---②whileポーリングのようにCPUを占有しない
条件が満たされると、スレッドBはnotify()通知スレッドAを呼び出し、通知スレッドAとは、スレッドAを起動し、実行可能な状態にすることである.
この方式の1つの利点は,CPUの利用率の向上である.
しかし、例えば、スレッドBが先に実行され、5つの要素が一気に追加され、notify()が呼び出されて通知が送信され、このときスレッドAが実行されるという欠点もある.スレッドAがwait()を実行して呼び出すと、それは永遠に起動されません.なぜなら、スレッドBはすでに通知を出しているので、これからは通知を出しません.これは、通知が早すぎると、プログラムの実行ロジックが乱れます.
④パイプ通信はjavaを使用する.io.PipedInputStreamとjava.io.PipedOutputStream通信
具体的には紹介しません.分散システムでいう2つの通信メカニズム:共有メモリメカニズムとメッセージ通信メカニズム.前の①のsynchronizedキーワードと②のwhileポーリングは「共有メモリメカニズム」に属しているような気がしますが、ポーリングの条件でvolatileキーワード修飾を用いた場合、この「共有の条件変数」が変わったかどうかを判断することでプロセス間のコミュニケーションが実現されることを示します.
パイプ通信は、メッセージングメカニズムに似ています.つまり、パイプを通じて、あるスレッドのメッセージを別のスレッドに送信します.
まとめ
以上がjavaスレッド間通信の通俗的な解釈のすべてであり,皆さんの役に立つことを願っている.興味のある方は引き続き当駅を参照してください.
Javaスレッド同期ロックコード例
Java作成と終了スレッドコードの例
JavaプログラミングにおけるCondition制御スレッド通信の実現
不足点があれば、コメントを歓迎します.