Javaマルチスレッド同時プログラミング、マルチスレッド最適化-ロック最適化

8253 ワード

JVM1.6時にsynchronizedを最適化し,スピンロック,適応スピンロック,ロック粗化,ロック除去,バイアスロック,軽量ロックを導入した.
スピンロック:
ロック待ちの場合は、CPUの実行権限を放棄せず、ビジーサイクルを行い、ロックを取得しようとします.デフォルトは10回です.-XX:PreBlockSpinパラメータで構成できます.
 
適応スピンロック:
スピンロックに基づいて,スピン回数を動的に調整した.スピンの回数は、前回の同じロック上のスピン回数とロックの所有者の状態によって決定される.前のスレッドがロックを正常に取得し、正常に動作する場合、今回のロックの取得の可能性が高いため、スピンの回数が比較的多い.前のスレッドがロックを成功させることが少ない場合、今回のロックを取得する確率も小さく、スピンを実行しない可能性があります.
 
ロックの太さ:
1つのコードで同じスレッドで同じオブジェクトのロックを繰り返し取得、解放すると、不要なパフォーマンスオーバーヘッドが発生するため、ロックの範囲を拡大する必要があり、同じオブジェクトのロック操作は1回のみ行い、ヘッダでロックを取得し、テールでロックを解放する必要があります.
 
ロック解除:
ロック消去とは、競合する共有データが存在しないことが検出されたロックを消去することである.
ロック消去は主に脱出分析によってサポートされ、スタック上の共有データが他のスレッドにアクセスされることができない場合は、プライベートデータとして扱うことができ、ロックを消去することができます.
 
偏向ロック、軽量ロック、重量級ロック:
初期JavaオブジェクトヘッダのMark wordではsynchronizedロックには,無ロック,偏向ロック,軽量レベルロック,重量レベルロックの4つの状態がある.
バイアスロック,軽量ロック,ヘビーロックの3つの形式は,それぞれ,ロックが1つのスレッドにのみ保持され,異なるスレッドが交互に保持され,マルチスレッド競合ロックの3つの場合に対応している.
偏向ロックの目的:多くの場合、ロックはマルチスレッド競合が存在しないだけでなく、常に同じスレッドによって複数回取得されるため、偏向ロックを導入することで、スレッドがロックを取得するコストがより低くなります.
偏向ロックは環境に競合がないと考えられ、ロックは1つのスレッドにのみ保持され、異なるスレッドがロックオブジェクトを取得または競合すると、偏向ロックは軽量レベルのロックにアップグレードされる.
バイアスロックは、マルチスレッド競合なしで不要な軽量ロック実行パスを低減することができる.
軽量ロックの目的:ほとんどの場合、同期ブロックは競合しません.ほとんどの場合、異なるスレッドが交互にロックを保持するため、軽量ロックを導入することで、スレッドのブロックに対する重量ロックのオーバーヘッドを低減できます.
軽量レベルのロックは,環境ではスレッドがロックオブジェクトに対する競合がほとんどないと考えられ,競合があっても(スピン)を少し待つだけでロックを得ることができるが,スピン回数に制限があり,それを超えるとヘビーレベルのロックにアップグレードされる.
ヘビー級ロックモニタロックMonito
 
1、ロックの最適化
1.1 synchronized最適化-臨界領域の減少
臨界領域を減らすことで、ロックが保持される時間を減らすことができ、ロックが徴用される確率を低減し、ロックオーバーヘッドを低減する目的を達成することができる.
実際のコード操作は次のとおりです.
オプティマイザコード:
	public synchronized void doSomething() {
		step1();
		syncStep2();
		step3();
	}

説明:コードではsyncStep 2を同期するだけで、他のstep 1、step 3は同期する必要はありません.では、syncStep 2だけをロックすることができます.
最適化後のコード:
    public void doSomething() {         step1();         synchronized (this) {             syncStep2();         }         step3();     } 
説明:synchronizedはメンバーメソッドで使用され、ロックオブジェクトは現在のオブジェクト(this)、ロック臨界領域はメソッド全体であり、すべての待機ロックはメソッド外でキューされます.
変更後:オブジェクトをロックするか、現在のオブジェクト(this)をロックしますが、ロックの臨界領域はsyncStep 2のみです.
なぜこのように書くと、ロックの性能が向上するのでしょうか.
答え:step 1実行に10 ms,syncStep 2実行に10 ms,step 3実行に10 msかかるとする.
ロック範囲がメソッド全体である場合、1つのロックが実行される時間は30 ms、すなわち次のロックがロックを取得するまで30 ms待つ必要がある.修正後、1つのロックが実行される時間は10 ms、すなわち次のロックは10 ms待つ必要があり、ロック性能が向上する.
 
1.1 synchronized最適化-ロックの粒度を低減
synchronizedを使用してリソースをロックする場合、私たちがよく使用するロックは、Class>をロックするか、thisをロックするか、作成したグローバルObjectをロックすることです.これらのロックの粒度は大きく、Class>とグローバルObjectはグローバルであり、仮想マシン全体がロックコードに実行されるまですべてのスレッドがキューに並ぶことを意味します.これが単一の例であれば、仮想マシン全体のスレッドがキューに並ぶことになります.
では、どのようにロックの粒度を減らす必要がありますか?
リソース競合がグローバルでない場合は、ロックの粒度を減らすことを考慮する必要があります.
2つの例を挙げます.
ユーザーがメールを送る操作は,メールを送る頻度を1分に1回しか送信できないように制限する必要がある.では、ロックをかけるときに粒度をロックするのは携帯電話番号です.
ユーザーの注文操作、需要はユーザーの注文頻度を制限し、業務上の注文の重複提出を防止することである.では、私たちのロック粒度はユーザー向けです.
次に,メール送信操作を例に,コードプレゼンテーションを行う.
変更前のコード:
package com.liyong.reactor.test;

import org.springframework.stereotype.Service;

@Service
public class MessageService {
	public synchronized boolean sendMsg(String mobile, String msg) {
		doValidate();
		doSend();
		doUpdateStatus();
		return true;
	}

	private void doUpdateStatus() {
		
	}

	private void doSend() {
		
	}

	private void doValidate() {
		
	}
}

説明:送信状態の検証、送信、更新は原子的な操作であるため、ここではすべてのロックの臨界領域を通じてロック最適化を行うことはできません.
最適化後:
package com.liyong.reactor.test;

import org.springframework.stereotype.Service;

@Service
public class MessageService {
	public boolean sendMsg(String mobile, String msg) {
		mobile = mobile.intern();
		synchronized (mobile) {
			doValidate();
			doSend();
			doUpdateStatus();
			return true;
		}
	}

	private void doUpdateStatus() {
		
	}

	private void doSend() {
		
	}

	private void doValidate() {
		
	}
}

説明:mobile.intern()のような一歩の操作は重要で、この方法によって、私たちが取得した文字列は仮想マシン定数領域のstringオブジェクトであり、仮想マシン全体にとって、字面量の同じstringはグローバルで唯一である.
Mobile文字列へのロックにより、グローバルなロック粒度を、携帯電話番号のみのロック粒度に変更します.
クラスタ導入時のロックはどのように解決しますか? 
分散ロックにより、現在のブログで解決されている問題は、単一の仮想マシンに対するロック最適化です.
 
1.3再ロック可能-RENTRAntLock
再読み込み可能:同じスレッドでロックを繰り返し取得できます.例えば、A->B->C A/B/Cの3つの方法は、同じ再入可能なロックが許可されていることを取得しなければならない.
コードインスタンス:ReentrantLockとConditionに基づいて接続プールのdemoを実現し、conditionによってスレッド間のインタラクションを実現する
package com.liyong.reactor.test;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConnectionPool {
	private final ReentrantLock lock = new ReentrantLock();
	private final Condition getCondition = lock.newCondition();
	
	private final LinkedList linkedList = new LinkedList<>(); 
	
	private final AtomicInteger counter = new AtomicInteger(2);
	
	public ConnectionPool(int poolSize) {
		if(poolSize > 0) {
			counter.set(poolSize);
		} else {
			poolSize = counter.get();
		}
		for (int i = 0; i < poolSize; i++) {
			linkedList.add(new Object());
		}
	}
	
	private Object getConnection(long timeout) {
		lock.lock();
		try {
			while(counter.get() == 0) {
				try {
					boolean result = getCondition.await(timeout, TimeUnit.SECONDS);
					if(!result) {
						System.err.println(Thread.currentThread().getName() + " timeout: " + timeout);
						return null;
					}
				} catch (InterruptedException e) {
					return null;
				}
			}
			counter.decrementAndGet();
			Object connection = linkedList.pollLast();
			System.out.println(Thread.currentThread().getName() + " get success: " + connection);
			return connection;
		} finally {
			lock.unlock();
		}
	}
	
	private void releaseConnection(Object connection) {
		lock.lock();
		try {
			linkedList.addFirst(connection);
			counter.incrementAndGet();
			getCondition.signal();
			System.out.println(Thread.currentThread().getName() + " release success: " + connection);
		} finally {
			lock.unlock();
		}
	}
	
	static class Worker extends Thread {
		private final ConnectionPool pool;
		public Worker(ConnectionPool pool) {
			this.pool = pool;
		}
		
		@Override
		public void run() {
			while(true) {
				long start = System.currentTimeMillis();
				Object connection = null;
				try {
					connection = pool.getConnection(1);
					if(connection == null) {
						System.err.println(Thread.currentThread().getName() + "       ,continue");
						continue;
					}
					try {
						sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					long end = System.currentTimeMillis();
					System.out.println(Thread.currentThread().getName() + " exe: " + (end - start) + "ms");
				} finally {
					if(connection != null) {
						pool.releaseConnection(connection);
					}
				}
			}
		}
	}
	
	
	public static void main(String[] args) {
		ConnectionPool pool = new ConnectionPool(2);
		
		for (int i = 0; i < 20; i++) {
			new Worker(pool).start();
		}
	}
}

 
1.4.リードライトロック-R e n t r a ntReadWriteLock
リード・ライト・ロック:つまり、リード・ロック、ライト・ロックをロックします.
では、いつ渋滞しますか?
Aはすでにリードロックを取得しており、Bはライトロックの取得を申請すると、Bはブロック、すなわちリード-ライトブロック
Aはすでに書き込みロックを取得しており、Bは読み取りロックを取得することを申請すると、Bはブロック、すなわち書き込み-読み取りブロックとなる
Aはすでに書き込みロックを取得しており、Bは書き込みロックの取得を申請すると、Bはブロック、すなわち書き込み-書き込みブロックとなる
いつ渋滞しないの?
Aはすでにリードロックを取得しており、Bはリードロックの取得を申請すると、Bはブロックされず、すなわちリード-リードがブロックされない
コード例:リード・ライト・ロックを使用してスレッドのセキュリティを保証するカウンタが実装されています.
package com.liyong.reactor.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Counter {
	private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	private final Lock readLock = readWriteLock.readLock();
	private final Lock writeLock = readWriteLock.writeLock();
	private volatile int count = 0;
	
	public void inc(int index) {
		writeLock.lock();
		try {
			count++;
		} finally {
			writeLock.unlock();
		}
	}
	
	public int get() {
		readLock.lock();
		try {
			return count;
		} finally {
			readLock.unlock();
		}
	}
}