複数のスレッド間のデータ通信方法
生産者/消費者モデル
生産/消費者の問題は非常に典型的なマルチスレッドの問題であり、関係するオブジェクトには、生産者、消費者、倉庫、および製品が含まれます。それらの関係は次のとおりです。
- プロデューサーは、倉庫が満杯でない場合にのみ生産し、倉庫が満杯になると生産を停止します。
- 消費者は、倉庫に製品がある場合にのみ消費し、倉庫が空になるまで待つことができます。
- 消費者が倉庫に消費する製品がないことに気付いた場合、生産者は生産するように通知されます。
- 生産者が消耗品を生産するとき、彼はそれを消費するように待っている消費者に通知する必要があります。
コーディングの実装
倉庫カテゴリ、製品は倉庫の属性データです
//临界资源
public class Basket {
private volatile Object data;
//生产者向仓库中存放数据
public synchronized void product(Object data) {
//如果仓库中有数据,则生产者进入阻塞等待,直到其它线程唤醒
while(this.data!=null)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果没有数据则进行生产操作
this.data=data;
System.out.println(Thread.currentThread().getName()+"生产了一个日期"+this.data);
this.notifyAll();//唤醒在当前对象上处于wait的所有线程
}
//消费者从仓库中消费数据
public synchronized void consume() {
//如果仓库中没有数据,则消费者进入阻塞等待,直到其它线程唤醒
while(this.data==null)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果有数据data!=null,则执行消费操作
System.out.println(Thread.currentThread().getName()+"消费了一个数据"+this.data);
this.data=null;
this.notifyAll(); //唤醒在当前对象上处于wait的所有线程
}
}
プロデューサースレッドは、製品の生産と消費者との倉庫の共有を担当します
public class Producer extends Thread {
private Basket basket;
//通过构造器传入对应的basket对象
public Producer(Basket basket) {
this.basket=basket;
}
@Override
public void run() {
//生产20次日期对象
for(int i=0;i<20;i++) {
Object data=new Date(); //生产者生产的具体产品
basket.product(data);
}
}
}
コンシューマースレッドは、製品を消費し、倉庫をプロデューサーと共有する責任があります
public class Consumer extends Thread {
private Basket basket;
public Consumer(Basket basket) {
this.basket = basket;
}
@Override
public void run() {
// 消费20次日期对象
for (int i = 0; i < 20; i++) {
basket.consume();
}
}
}
プロデューサーとコンシューマーは同じ期間に同じストレージスペースを共有します。プロデューサーはストレージスペースに製品を追加し、コンシューマーはストレージスペースから製品を削除します。ストレージスペースが空の場合、コンシューマーはブロックし、ストレージスペースがいっぱいになるとプロデューサーはブロックされています。
生産者/消費者モデルを使用する理由
スレッド化された世界では、プロデューサーはデータを生成するスレッドであり、コンシューマーはデータを消費するスレッドです。マルチスレッド開発では、プロデューサーの処理速度は非常に速いが、コンシューマーの処理速度は非常に遅い場合、プロデューサーは、データの生成を続行する前に、コンシューマーが処理を終了するのを待つ必要があります。同様に、消費者の処理能力が生産者の処理能力よりも大きい場合、消費者は生産者を待たなければなりません。生産能力と消費能力の不均衡というこの問題を解決するために、生産者と消費者のモデルがあります。
生産者/消費者モデルの利点
1.デカップリング。余分なバッファがあるため、プロデューサーとコンシューマーは直接呼び出しません。これは簡単に考えられます。このように、プロデューサーとコンシューマーのコードは互いに影響しません。実際、プロデューサーとコンシューマーは消費者は影響を受けません。消費者と消費者の間の強い結合が解かれ、それが生産者とバッファ/消費者とバッファの間の弱い結合になります。
2.生産者/消費者の処理能力のバランスをとることにより、全体的なデータ処理速度を向上させます。これは、生産者/消費者モデルの最も重要な利点の1つです。コンシューマーがプロデューサーから直接データを取得する場合、プロデューサーの生産速度が非常に遅いが、コンシューマーの消費速度が非常に速い場合、コンシューマーはCPUタイムスライスを取得し、そこで何も待たなければなりません。プロデューサー/コンシューマーモデルでは、プロデューサーとコンシューマーは2つの独立した同時エンティティです。プロデューサーは、コンシューマーに関係なく、生成されたデータをバッファーにスローします。コンシューマーもバッファーから取得します。データを取得するだけです。生産者を気にする必要はありません。バッファがいっぱいの場合は生産が行われず、バッファが空の場合は消費が行われないため、生産者/消費者の処理能力は動的なバランスに達することができます。
生産者/消費者モデルの役割
- 並行性をサポートする
- デカップリング
- 不均一な可用性をサポート
wait / notifyなどの呼び出しメソッドは、同期メソッドなど、現在のスレッドオブジェクト内にある必要があります
シングルトン
VMにインスタンスが1つしかないことを確認します
- 空腹の男モード
- プライベートコンストラクター
- プライベート静的シングルトンインスタンス=新しいシングルトン();
- 共有静的メソッド
- 怠惰な男モード
- プライベートコンストラクター
- プライベート静的プロパティ。オブジェクトを直接作成しないでください
- 共有静的メソッド
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static Singleton getInstance() {
if(instance==null)
instance=new Singleton(); //当第一次使用对象时才进行创建
return instance;
}
}
オブジェクトの複数の作成があるかもしれません、それを解決する方法
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public synchronized static Singleton getInstance() {
if(instance==null)
instance=new Singleton(); //当第一次使用对象时才进行创建
return instance;
}
}
一般に、同時実行性が影響を受けるため、より詳細なロック処理メカニズムを使用することはお勧めしません。
二重検出レイジーモード
-
無駄な作成操作を回避するために、オンデマンドでオブジェクトを作成します
-
スレッドセーフ
public class Singleton{
private Singleton(){
}
private static Singleton instance=null;
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
return instance;
}
}
ロックの使用
Lockは、Java 1.5で導入されたスレッド同期ツールです。主に、複数のスレッドで共有リソースを制御するために使用されます。本質的に、ロックは単なるインターフェースです。同期は、同期ロックオブジェクトを明示的に定義することで実現できます。同期よりも幅広いロック操作を提供でき、複数の関連する条件オブジェクトをサポートします。
- void lock();ロックの取得を試み、取得が成功した場合は戻り、それ以外の場合は現在のスレッドをブロックします
void lockInterruptibly()はInterruptedExceptionをスローします。ロックを取得しようとすると、ロックを正常に取得する前にスレッドが中断され、ロックの取得をあきらめて例外をスローします。
boolean tryLock();ロックの取得を試みます。ロックが正常に取得された場合はtrueを返し、それ以外の場合はfalseを返します。
boolean tryLock(long time、TimeUnit unit)は、ロックの取得を試みます。指定された時間内にロックを取得するとtrueを返し、それ以外の場合はfalseを返します。ロックを取得する前に中断すると、例外がスローされます。
- voidunlock();ロックを解除します
- Condition newCondition();は、現在のロックの条件変数を返します。notifyおよびwaitと同様の機能は、条件変数を介して実現できます。ロックには、複数の条件変数を含めることができます。
Lockには3つの実装クラスがあります。1つはReentrantLockで、他の2つはReentrantReadWriteLockクラスの2つの静的内部クラスReadLockとWriteLockです。
使用法:マルチスレッドで(相互に排他的な)共有リソースにアクセスする場合は、アクセス前にロックし、アクセス後にロックを解除します。ロック解除操作は、finallyブロックに配置することをお勧めします。
プライベートファイナルReentrantLocklock = new ReentrantLock();
特定のメソッドlock.lock()でtry {} finally {lock.unlock}
Lock lock=new ReentrantLock();//构建锁对象
try{
lock.lock();//申请锁,如果可以获取锁则立即返回,如果锁已经被占用则阻塞等待
System.out.println(lock);//执行处理逻辑
} finally{
lock.unlock();//释放锁,其它线程可以获取锁
}
例1:4つのスレッドを開始し、整数に対して50の加算および減算操作を実行します。出力のスレッドセーフを確保するには、2つの加算と2つの減算が必要です。
public class OperNum {
private int num=0;
private final static Lock lock=new ReentrantLock(); //构建锁对象
public void add() {
try {
lock.lock(); //申请加锁操作,如果能加上则立即返回,否则阻塞当前线程
num++;
System.out.println(Thread.currentThread().getName()+"add..."+num);
} finally {
lock.unlock(); //具体实现采用的是重入锁,所以持有锁的线程可以多次申请同一个锁,但是申请加锁次数必须和释放锁的次数一致
}
}
public void sub() {
try {
lock.lock();
num--;
System.out.println(Thread.currentThread().getName()+"sub..."+num);
} finally {
lock.unlock();
}
}
}
条件インターフェース
条件は、jucパッケージで提供されるインターフェースです。条件オブジェクトに変換できます。その機能は、スレッドが最初に待機し、特定の条件が外部で満たされると、待機中のスレッドが条件オブジェクトを介して目覚めます。
void await()throws InterruptedException;スレッドを待機状態に入れます。他のスレッドが同じConditionオブジェクトのnotify / notifyAllを呼び出すと、待機中のスレッドが起動される場合があります。ロックを解除します
void signal();待機中のスレッドをウェイクアップします
void signalAll();すべてのスレッドをウェイクアップします
使用条件の特記事項:
- スレッドをブロックするためにcondition.await()が呼び出されると、lock.lock()がいくつ呼び出されても、ロックは自動的に解放され、lock.lock()メソッドでブロックされたスレッドはロックを取得できます。
- スレッドをウェイクアップするためにcondition.signal()が呼び出されると、最後にブロックされた位置で実行が継続され、デフォルトでロックが自動的に再取得されます(ブロック中に取得されるロックの数は同じであることに注意してください)。