Javaマルチスレッドの表示ロックと内蔵ロックの概要

10541 ワード

マルチスレッドの表示ロックと組み込みロックの概要
JavaにはSynchronizedで実装された内蔵ロックとReentrantLockで実装されたディスプレイロックがあり,この2つのロックにはそれぞれメリットがあり,互いに補完されているが,この文章はまとめている.
*Synchronized*
内蔵ロック取得ロックとリリースロックは暗黙的であり、synchronized修飾コードに入るとロックが取得され、対応するコードを出るとロックが解放される.

synchronized(list){ //   
  list.append();
  list.count();
}//   
つうしん
Synchronizedと組み合わせて使用される通信方法は、wait()、notify()が一般的である.
wait()メソッドは、現在のロックをすぐに解放し、対応するnotifyまで待機し、ロックを再取得してから実行を続行します.notify()はすぐにロックを解除することはなく、notify()が存在するスレッドがsynchronizedブロック内のすべてのコードを実行してから解放される必要があります.次のコードで検証します.

public static void main(String[] args){
	List list = new LinkedList();
	Thread r = new Thread(new ReadList(list));
	Thread w = new Thread(new WriteList(list));
	r.start();
	w.start();
}
class ReadList implements Runnable{
	private List list;
	public ReadList(List list){
		this.list = list;
	}
	@Override
	  public void run(){
		System.out.println("ReadList begin at "+System.currentTimeMillis());
		synchronized (list){
			try {
				Thread.sleep(1000);
				System.out.println("list.wait() begin at "+System.currentTimeMillis());
				list.wait();
				System.out.println("list.wait() end at "+System.currentTimeMillis());
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("ReadList end at "+System.currentTimeMillis());
	}
}
class WriteList implements Runnable{
	private List list;
	public WriteList(List list){
		this.list = list;
	}
	@Override
	  public void run(){
		System.out.println("WriteList begin at "+System.currentTimeMillis());
		synchronized (list){
			System.out.println("get lock at "+System.currentTimeMillis());
			list.notify();
			System.out.println("list.notify() at "+System.currentTimeMillis());
			try {
				Thread.sleep(2000);
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("get out of block at "+System.currentTimeMillis());
		}
		System.out.println("WriteList end at "+System.currentTimeMillis());
	}
}
実行結果:

ReadList begin at 1493650526582
WriteList begin at 1493650526582
list.wait() begin at 1493650527584
get lock at 1493650527584
list.notify() at 1493650527584
get out of block at 1493650529584
WriteList end at 1493650529584
list.wait() end at 1493650529584
ReadList end at 1493650529584
読み取りスレッドが実行され始め、waitが開始されると、書き込みスレッドがロックされます.書き込みスレッドがnotifyではなく同期ブロックから出た後、読み取りスレッドはwaitで終了し、ロックが取得されます.だからnotifyはロックを解放せず、waitはロックを解放します.特筆すべきは、notifyall()が待機キュー内のすべてのスレッドを通知することです.
エンコーディング
符号化モードは比較的簡単で、単一で、表示する必要のないロックを取得し、ロックを解除し、不注意でロックを解除する誤りを低減することができる.使用パターンは次のとおりです.

synchronized(object){ 
 
}
柔軟性
1.内蔵ロック同期ブロックに入る時、無限待ちの策略を採用して、いったん待ち始めると、中断もキャンセルもできなくて、飢餓とデッドロックの問題が発生しやすい2.スレッドがnotify方法を呼び出す時、ランダムに相応のオブジェクトの待ち行列の1つのスレッドを選択してそれを呼び覚ますことができて、FIFOの方式に従うのではありませんて、強い公平性の要求があるならば、例えばFIFOでは満足できない
パフォーマンス
SynchronizedはJDK 1.5以前は性能(主にスループットを指す)が悪く、拡張性もReentrantLockに及ばない.しかし、JDK 1.6以降は、内蔵ロックを管理するアルゴリズムが修正され、Synchronizedと標準のReentrantLockの性能の差は大きくない.
*ReentrantLock*
ReentrantLockは表示ロックであり、lockおよびunlock操作を行う必要があります.
つうしん
ReentrantLockと組み合わせた通行方法はConditionで、以下の通りです.

private Lock lock = new ReentrantLock(); 
private Condition condition = lock.newCondition(); 
condition.await();//this.wait(); 
condition.signal();//this.notify(); 
condition.signalAll();//this.notifyAll();
Conditionはロックにバインドされており、Conditionを作成するにはlock.newCondition()を使用する必要があります.上のコードから分かるように、Synchronizedが実現できる通信方式は、Conditionが実現でき、機能が似たコードが同じ行に書かれている.Conditionの優れた点は、オブジェクトの読み取り/書き込みCondition、キューの空/満Conditionなど、複数のスレッド間で異なるConditionを確立できることです.JDKソースコードのArrayBlockingQueueでは、この特性が使用されています.

public ArrayBlockingQueue(int capacity, boolean fair) {
  if (capacity <= 0)
    throw new IllegalArgumentException();
  this.items = new Object[capacity];
  lock = new ReentrantLock(fair);
  notEmpty = lock.newCondition();
  notFull = lock.newCondition();
}
public void put(E e) throws InterruptedException {
  checkNotNull(e);
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();
  try {
    while (count == items.length)
      notFull.await();
    enqueue(e);
  } finally {
    lock.unlock();
  }
}
public E take() throws InterruptedException {
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();
  try {
    while (count == 0)
      notEmpty.await();
    return dequeue();
  } finally {
    lock.unlock();
  }
}
private void enqueue(E x) {
  // assert lock.getHoldCount() == 1;
  // assert items[putIndex] == null;
  final Object[] items = this.items;
  items[putIndex] = x;
  if (++putIndex == items.length)
    putIndex = 0;
  count++;
  notEmpty.signal();
}
private E dequeue() {
  // assert lock.getHoldCount() == 1;
  // assert items[takeIndex] != null;
  final Object[] items = this.items;
  @SuppressWarnings("unchecked")
  E x = (E) items[takeIndex];
  items[takeIndex] = null;
  if (++takeIndex == items.length)
    takeIndex = 0;
  count--;
  if (itrs != null)
    itrs.elementDequeued();
  notFull.signal();
  return x;
}
エンコーディング

Lock lock = new ReentrantLock();
lock.lock();
try{
 
}finally{
  lock.unlock();
}
Synchronizedよりも複雑で、finallyでロックを他の場所ではなく解放することを覚えておくと、異常が発生してもロックを解放することができます.
柔軟性
1.lock.lockInterruptibly()は、スレッドがロック待ちで応答割り込みをサポートするようにすることができる.lock.tryLock()は、スレッドが一定時間待機した後、ロックがまだ取得されていない場合、待機を停止させることができる.この2つのメカニズムがあれば、盲目的に待つのではなく、ロックを得る再試行メカニズムをよりよく制定することができ、飢餓とデッドロックの問題をよりよく避けることができる.
2.ReentrantLockは公正ロック(デフォルトではない)となり、公平ロックとはロックの待機キューのFIFOであるが、公平ロックは性能消費をもたらし、必要でなければ使用を推奨しない.これはCPUが命令を並べ替える理由と似ており,コードの書き順に命令を強引に実行すると,多くのクロックサイクルが浪費され,最大利用率に達しない.
パフォーマンス
Synchronizedと標準のReentrantLockの性能の差は大きくありませんが、ReentrantLockは反発しない読み書きロックも提供しています.
つまり、毎回最大1つのスレッドだけがロックを保持できるように強制しないと、読み取り/書き込みの競合、書き込みの競合は回避されますが、読み取り/読み取りの競合は排除されません.
読み取り/読み取りはデータの整合性に影響しないため、複数の読み取りスレッドが同時にロックを持つことができ、読み取り/書き込みが比較的高い場合にパフォーマンスが大幅に向上します.
次の2つのロックで実装されるスレッドセキュリティのlinkedlist:

class RWLockList {
	//   
	private List list;
	private final ReadWriteLock lock = new ReentrantReadWriteLock();
	private final Lock readLock = lock.readLock();
	private final Lock writeLock = lock.writeLock();
	public RWLockList(List list){
		this.list = list;
	}
	public int get(int k) {
		readLock.lock();
		try {
			return (int)list.get(k);
		}
		finally {
			readLock.unlock();
		}
	}
	public void put(int value) {
		writeLock.lock();
		try {
			list.add(value);
		}
		finally {
			writeLock.unlock();
		}
	}
}
class SyncList {
	private List list;
	public SyncList(List list){
		this.list = list;
	}
	public synchronized int get(int k){
		return (int)list.get(k);
	}
	public synchronized void put(int value){
		list.add(value);
	}
}
読み書きロックテストコード:

List list = new LinkedList();
for (int i=0;i<10000;i++){
	list.add(i);
}
RWLockList rwLockList = new RWLockList(list);
//     
Thread writer = new Thread(new Runnable() {
	@Override
	  public void run() {
		for (int i=0;i<10000;i++){
			rwLockList.put(i);
		}
	}
}
);
Thread reader1 = new Thread(new Runnable() {
	@Override
	  public void run() {
		for (int i=0;i<10000;i++){
			rwLockList.get(i);
		}
	}
}
);
Thread reader2 = new Thread(new Runnable() {
	@Override
	  public void run() {
		for (int i=0;i<10000;i++){
			rwLockList.get(i);
		}
	}
}
);
long begin = System.currentTimeMillis();
writer.start();
reader1.start();
reader2.start();
try {
	writer.join();
	reader1.join();
	reader2.join();
}
catch (InterruptedException e) {
	e.printStackTrace();
}
System.out.println("RWLockList take "+(System.currentTimeMillis()-begin) + "ms");
同期ロックテストコード:

List list = new LinkedList();
for (int i=0;i<10000;i++){
  list.add(i);
}
SyncList syncList = new SyncList(list);//     
Thread writerS = new Thread(new Runnable() {
  @Override
  public void run() {
    for (int i=0;i<10000;i++){
      syncList.put(i);
    }
  }
});
Thread reader1S = new Thread(new Runnable() {
  @Override
  public void run() {
    for (int i=0;i<10000;i++){
      syncList.get(i);
    }
  }
});
Thread reader2S = new Thread(new Runnable() {
  @Override
  public void run() {
    for (int i=0;i<10000;i++){
      syncList.get(i);
    }
  }
});
long begin1 = System.currentTimeMillis();
writerS.start();reader1S.start();reader2S.start();
try {
  writerS.join();
  reader1S.join();
  reader2S.join();
} catch (InterruptedException e) {
  e.printStackTrace();
}
System.out.println("SyncList take "+(System.currentTimeMillis()-begin1) + "ms");
結果:

RWLockList take 248ms
RWLockList take 255ms
RWLockList take 249ms
RWLockList take 224ms
 
SyncList take 351ms
SyncList take 367ms
SyncList take 315ms
SyncList take 323ms
読み書きロックは確かに純粋な反発ロックより優れていることがわかる.
まとめ
内蔵ロックの最大の利点は簡潔で使いやすく、表示ロックの最大の利点は機能が豊富であるため、内蔵ロックで内蔵ロックを使うことができ、内蔵ロック機能が満たされない場合は表示ロックを考慮する.
以上、Javaマルチスレッドの表示ロックと内蔵ロックについて詳しく説明したすべての内容をまとめました.興味のある方は引き続き当駅を参照してください.
Javaマルチスレッド割り込みメカニズムの3つの方法と例
Javaマルチスレッドの利点とコードの例を簡単に説明します
Javaはfutureを利用してマルチスレッドの実行結果をタイムリーに取得する
不足点があれば、コメントを歓迎します.