Javaスレッド同時ロック-ReentrantLock(再ロック)原理の詳細


ReentrantLockは再ロックであり、スレッドによるリソースの重複ロックをサポートし、公平ロックと非公平ロックもサポートします.synchronizedキーワードは、synchronized修飾の再帰メソッドのように暗黙的に再アクセスをサポートし、メソッドの実行時に実行スレッドは、ロックを取得した後もそのロックReentrantLockを連続的に複数回取得することができる.synchronizedキーワードのように暗黙的な再アクセスをサポートすることはできないが、lock()メソッドを呼び出すと、ロックのスレッドが取得され、ロック()メソッドは、ブロックされずにロックを取得するために再び呼び出すことができる.
フラットロック
定義#テイギ#
フェアロックは、絶対時間においてロック取得の要求が必ず先に満たされ、フェアな取得ロック、すなわち待ち時間が最も長いスレッドが最も先にロックを取得するのが最適であり、ロック取得時の順序とも言える.ReentrantLockは、フェアロックの有無を制御できるコンストラクション関数を提供します.利点と欠点は非公平ロック効率が高いわけではないが,飢餓の発生確率を低減でき,待ち時間が長いスレッドほどロックが容易に得られる.
再アクセスの実現
再アクセスとは、任意のスレッドがロックを取得した後にロックを再取得することができ、ロックによってブロックされたスレッドによってロックを再取得することはありません.ロックは、ロックを取得したスレッドが現在の占有ロックのスレッドであるかどうかを識別する必要があります.もしそうであれば、再取得に成功します.ロックの最終的な解放:スレッドはn回繰り返してロックを取得し、その後、n回目にロックを解放した後、他のスレッドはロックを取得することができる.ロックの最終的な解放は、ロックが取得に対してカウント自増することを要求し、カウントは現在のロックが繰り返し取得された回数を表し、ロックが解放された場合、カウント自減し、カウントが0に等しい場合、ロックが正常に解放されたことを示す.次に、非公平に同期状態を取得するコードの例を見てみましょう.
       /**
         * Performs non-fair tryLock.  tryAcquire is
         * implemented in subclasses, but both need nonfair
         * try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //           
            if (c == 0) {
                //           ,    
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //          ,              ,         
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

ロックの取得に成功したスレッドは、同期状態の値を増加させただけで、同期状態を解放するときに同期状態を減少させるReentrantLockを要求し、ロックコードを解放するには、次のようにします.
       protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //                      
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //        0       
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //       ,  0   
            setState(c);
            return free;
        }

ロックがn回取得された場合、前(n−1)回tryRelease(int release)メソッドはfalseを返さなければならず、同期状態が完全に解放されてこそtrueを返すことができる.この方法は、同期状態が0であるか否かを最終的な解放条件とし、同期状態が0である場合、占有スレッドをnullに設定し、trueを返し、解放に成功したことを示す.
フェアロックと非フェアロックの違い
ロックが公平である場合、ロックの取得順序は要求の絶対時間順序に合致する必要があります.つまり、FIFOは非公平ロックに対してCASが同期状態を設定することに成功すれば、現在のスレッドがロックを取得したことを示します.公平ロックには違いありません.より長い間待っていたスレッドも考慮しなければなりません.公平ロックのソースコードを見てみましょう.
      protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //             ,                     ,              ,               
                //             
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

次に、公平ロックと非公平ロックの違いのコード例を書きます.
public class FairAndUnfairTest {
    /**
     * public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        }
     */
    private static Lock fairLock = new ReentrantLockTest(true);
    private static Lock unfairLock = new ReentrantLockTest(false);

    public static void main(String[] args) {
        testLock(unfairLock);
//      testLock(fairLock);
    }

    public static void testLock(Lock lock){
        for(int i=0;i<10;i++){
             new Thread(new Job(lock),i+"").start();
        }
    }

    private static class Job extends Thread{
        private Lock lock;
        public Job(Lock lock){
            this.lock = lock;
        }
        public void run(){
            lock.lock();
            try {
                //         Tread     Thread
                System.out.println("Lock by ['" + Thread.currentThread().getName() + "'],and waiting "+((ReentrantLockTest)lock).getQueuedTheads());
            } finally {
                lock.unlock();
            }
        }

    }

    @SuppressWarnings("serial")
    private static class ReentrantLockTest extends ReentrantLock{
        public ReentrantLockTest(boolean fair) {
            super(fair);
        }
        public Collection getQueuedTheads(){
            List list = new ArrayList(super.getQueuedThreads());
            //       
            Collections.reverse(list);
            return list;
        }
    }

}

非フェアロックの実行結果は、スレッドがロックを取得していないため、順序に従っていません.
Lock by ['0'],and waiting [Thread[1,5,main], Thread[2,5,main]]
Lock by ['7'],and waiting [Thread[1,5,main], Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main]]
Lock by ['1'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['2'],and waiting [Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['3'],and waiting [Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['4'],and waiting [Thread[5,5,main], Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['5'],and waiting [Thread[6,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['6'],and waiting [Thread[8,5,main], Thread[9,5,main]]
Lock by ['8'],and waiting [Thread[9,5,main]]
Lock by ['9'],and waiting []

フェアロックの実行結果は次のとおりです.スレッドは順番にロックを取得します.
Lock by ['0'],and waiting [Thread[1,5,main], Thread[2,5,main]]
Lock by ['1'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main]]
Lock by ['2'],and waiting [Thread[3,5,main], Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['3'],and waiting [Thread[4,5,main], Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['4'],and waiting [Thread[5,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['5'],and waiting [Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['6'],and waiting [Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
Lock by ['7'],and waiting [Thread[8,5,main], Thread[9,5,main]]
Lock by ['8'],and waiting [Thread[9,5,main]]
Lock by ['9'],and waiting []

フェアロックを複数回実行すると、たまに順番に合わないスレッドが表示されたり、次の結果が表示されたりします.理由を知っていればメッセージを残してもいいです.ありがとうございます.
Lock by ['1'],and waiting [Thread[0,5,main], Thread[2,5,main]]
Lock by ['0'],and waiting [Thread[2,5,main], Thread[3,5,main], Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['2'],and waiting [Thread[3,5,main], Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['3'],and waiting [Thread[5,5,main], Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['5'],and waiting [Thread[4,5,main], Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['4'],and waiting [Thread[7,5,main], Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['7'],and waiting [Thread[6,5,main], Thread[9,5,main], Thread[8,5,main]]
Lock by ['6'],and waiting [Thread[9,5,main], Thread[8,5,main]]
Lock by ['9'],and waiting [Thread[8,5,main]]
Lock by ['8'],and waiting []

理論上、非公平ロックは同じスレッドで連続的にロックを取得することができますが、私はここではしばらくシミュレーションしていません.もし良い方法があれば、伝言を残すことができます.公平ロックはFIFOの原則に従ってロックの取得を保証してくれて、代価は大量のスレッドの切り替えを行うことです.非公平性ロックは、スレッドの「飢餓」を引き起こす可能性がありますが、スレッドの切り替えが少なく、スループットが向上します.