Java同時実装リエントロック可能(LockとAQSを使用して実装)


再入可能ロックとは、スレッドがロックを取得した場合、スレッドが先にロックを取得する必要があることを前提として、このロックに複数回アクセスできることを意味します.
再入可能ロックは最もよく使われるロックであり、Javaの内蔵ロックは再入可能ロックであり、synchronizedキーワードを使用して内蔵ロックメカニズムを有効にすることができる.例えば、1つのクラスには2つのsynchronizedメソッドAとBがあり、AメソッドではBメソッドが呼び出され、ロックが再入可能ではない場合、Bを尋ねるときはAロックの解放を待つ必要があり、無期限に待つ必要がある.
後で、再入力可能なロックを自分で実現します.
実装手順または構想:
1.Threadリファレンスを使用してロックを取得するスレッドを指し、lock()メソッドの内部で、現在のスレッドがロックを持つスレッドと等しいかどうか、等しくない場合wait()、等しい実行ステップ2、unlock()メソッドの内部で、現在のスレッドがロックを持つスレッドと等しいかどうかを判断し、等しい場合ステップ2を実行します.
2.カウンタlockCountを使用して、1つのスレッドがロックに入る回数を記録し、ロックを正常に取得すると自増し、ロックを解除すると自減し、lockCountが0に等しい場合、空き状態を表し、notify()を呼び出してスレッドを起動し、isLockをfalseに設定することができ、1回のロック解除方法が完了し、ロックを再競争できることを示します.
 
カスタム再ロッククラス
package cn.itcats.thread.safe.Test3;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
	//     
	private boolean isLock = false;
	
	private Thread lockBy = null;
	
	private int lockCount = 0;

	/**
	 *   wait()     ,      lock()     ,         ,        synchronized
	 */
	public synchronized void lock() {
		Thread currentThread = Thread.currentThread();
		//      lock     ,  
		while (isLock && currentThread != lockBy) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//          
		isLock = true;
		lockBy = currentThread;
		lockCount++;
	}

	public synchronized void unlock() {
		if(lockBy==Thread.currentThread()) {
			lockCount--;
			//   
			if(lockCount == 0) {
				notify();
				isLock = false;
			}
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {

	}

	@Override
	public boolean tryLock() {
		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return false;
	}

	@Override
	public Condition newCondition() {
		return null;
	}

}

 
テストクラス
package cn.itcats.thread.safe.Test3;
//      
public class Reentry {
	private MyLock lock = new MyLock();
	public void a() {
		lock.lock();
		System.out.println("a");
		b();
		lock.unlock();
	}
	
	public void b() {
		lock.lock();
		System.out.println("b");
		lock.unlock();
	}
	
	public static void main(String[] args) {
		Reentry reentry = new Reentry();
		new Thread(new Runnable() {
			public void run() {
				reentry.a();
			}
		}).start();
	}
}

 
AQSを使用して再ロックを実現
AQSは、フロントアウト(FIFO)待ち行列のブロックロックと関連する同期器(信号量、イベントなど)に依存するフレームワークであることが、公式APIドキュメントを参照してください.
先進的なリードアウト(FIFO)待ち行列に依存するブロッキング・ロックと相関同期器を実現するために(信号量、イベントなど)フレームワークを提供します.このような設計目標は、状態を表す単一の原子int値に依存するほとんどの同期器の有用な基礎になることです.サブクラスは、この状態を変更する保護方法を定義し、どの状態がこのオブジェクトに対して取得または解放を意味するかを定義する必要があります.これらの条件の後、このような他の方法はすべてのキューとブロックメカニズムを実現できます.サブクラスは他のステータスフィールドを維持することができるが、同期を得るためにのみgetState()setState(int)compareAndSetState(int, int)メソッドを使用して原子的に更新されたint値を操作する. 
サブクラスは、閉じたクラスの同期プロパティを実現するために使用できる非共通の内部ヘルプクラスとして定義する必要があります.クラスAbstractQueuedSynchronizerは同期インタフェースを実装していません.むしろ、acquireInterruptibly(int)のようないくつかの方法が定義されており、適切な場合には、その共通の方法を実現するために、特定のロックおよび関連する同期器によって呼び出すことができる. 
このようなサポートは、デフォルトの独占モードと共有モードのいずれか、または両方をサポートします.排他モードでは、他のスレッドがロックを取得しようとすると成功しません.共有モードでは、複数のスレッドがロックを取得すると、必ずしも成功するわけではありません.このような違いは、共有モードでロックが正常に取得された場合、次の待機スレッドが機械的に認識される以外は「理解」されません.(存在する場合)また、自分がロックを正常に取得できるかどうかを決定する必要があります.異なるモードの待機スレッドは同じFIFOキューを共有できます.通常、実装サブクラスはいずれかのモードのみをサポートしますが、両方のモードは(例)ReadWriteLockで機能します.排他モードのみをサポートするか、共有モードのみをサポートするサブクラスは未使用モードをサポートする方法を定義する必要はありません.
このような排他モードをサポートするサブクラスによってネストされたAbstractQueuedSynchronizer.ConditionObjectクラスが定義され、このクラスをCondition実装として使用することができる.isHeldExclusively()メソッドは、現在のスレッドに対して同期が排他的であるか否かを報告する.現在getState()値呼び出しrelease(int)メソッドを使用すると、このオブジェクトを完全に解放できます.保存された状態値が与えられた場合、acquire(int)メソッドは、このオブジェクトを最終的に以前に取得した状態に戻すことができる.このような条件を作成するAbstractQueuedSynchronizerメソッドは他にありません.したがって、この制約が満たされない場合は使用しないでください.AbstractQueuedSynchronizer.ConditionObjectの動作は、当然、その同期器が実現する意味に依存する. 
これにより、内部キューのチェック、検出、監視方法が提供され、conditionオブジェクトにも同様の方法が提供されます.これらのメソッドは、必要に応じて、その同期メカニズムに使用されるAbstractQueuedSynchronizerを使用してクラスにエクスポートできます. 
このようなシーケンス化は、メンテナンス状態のベース原子の整数のみを格納するため、シーケンス化されたオブジェクトには空のスレッドキューがあります.シーケンス化が必要な典型的なサブクラスでは、逆シーケンス化時にオブジェクトを既知の初期状態に戻すreadObjectメソッドが定義されます.
 
 
具体的なコード実装:
package cn.itcats.thread.safe.Test4;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {

	private Sync sync =new Sync();

	//                  ,                
	private class Sync extends AbstractQueuedSynchronizer {

		//        
		protected boolean tryAcquire(int arg) {
			//           ,  true
			//           ,  ,  false.      ,                 ,      ,        
			//                
			int state = getState();
			Thread currentThread = Thread.currentThread();
			
			if (state == 0) {
				if (compareAndSetState(0, arg)) {
					setExclusiveOwnerThread(currentThread);
					return true;
				}
			}
			else if(getExclusiveOwnerThread() == currentThread){
				//                 (  ),      ,        
				setState(state + 1);
				return true;
			}
			return false;
		}

		protected boolean tryRelease(int arg) {
			//               ,              
			if (Thread.currentThread() != getExclusiveOwnerThread()) {
				throw new RuntimeException("          ,     ");
			}
			int state = getState() - arg;
			boolean flag = false;

			//        1
			if (state == 0) {
				setExclusiveOwnerThread(null);
				flag = true;
			}
			setState(state);
			return flag;
		}

		Condition getCondition() {
			return new ConditionObject();
		}
	}

	@Override
	public void lock() {
		sync.acquire(1);
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
	}

	@Override
	public boolean tryLock() {
		return sync.tryAcquire(1);
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
	}

	@Override
	public void unlock() {
		//       1
		sync.release(1);
	}

	@Override
	public Condition newCondition() {
		return sync.getCondition();
	}
}

 
スレッドのセキュリティとロックの再入力をテスト
package cn.itcats.thread.safe.Test4;

public class Sequence {
	private int value;
	private MyLock lock = new MyLock();

	public int getValue() {
		lock.lock();
		try {
			Thread.sleep(300);
			return value++;
		} catch (InterruptedException e) {
			throw new RuntimeException();
		}finally {
			lock.unlock();
		}
	}

	public static void main(String[] args) {
		Sequence sequence = new Sequence();
		
		/*new Thread(new Runnable() {

			public void run() {
				while (true)
					System.out.println(Thread.currentThread().getId() + "  " + sequence.getValue());
			}
		}).start();

		new Thread(new Runnable() {

			public void run() {
				while (true)
					System.out.println(Thread.currentThread().getId() + "  " + sequence.getValue());
			}
		}).start();*/
		
		new Thread(new Runnable() {
			public void run() {
				sequence.A();
			}
		}).start();
	}
	
	//       
	public void A() {
		lock.lock();
		System.out.println("A");
		B();
		lock.unlock();
	}
	
	public void B() {
		lock.lock();
		System.out.println("B");
		lock.unlock();
	}
}