Java ReentrantLock Class - Java 147

Java ReentrantLock Class – Java 147

Java ReentrantLock Class

實現執行緒同步的傳統方法,是使用 synchronized 關鍵字,雖然提供了一定的基本同步,但 synchronized 關鍵字的使用非常嚴格,例如,一個執行緒只能取得一次鎖,同步塊不提供任何等待佇列的機制,並且在一個執行緒退出後,任何執行緒都可以取得鎖,這可能會導致其他執行緒在很長一段時間內資源匱乏, ReentrantLock Java 提供了可重入的互斥鎖,又被稱為獨占鎖,實現了 Lock 介面,擁有與 synchronized 相同的同步加鎖功能,ReentrantLock Java Examples 本篇增加了範例,並透過單元測試來驗證產出結果。

檔案目錄

./
   +- src
       +- test
       |   +- org
       |       +- ruoxue
       |           +- java_147
       |               +- synchronization
       |                   +- reentrantlock
       |                       +- ReentrantLockClassTest.java   

單元測試

Java Lock 提供加鎖、解鎖等操作。

nonfairLock

Java Lock 建立 5 個執行緒,使用非公平鎖,對計數器加 1 ,然後結束任務。

	protected class NonfairLockWorker implements Runnable {

		private final Lock lock = new ReentrantLock();
		private int count;

		public NonfairLockWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] lock acquired");
				count++;
				System.out.println("T[" + Thread.currentThread().getId() + "] count: " + count);
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println("T[" + Thread.currentThread().getId() + "] lock released");
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void nonfairLock() {
		int expected = 5;
		int taskSize = 5;
		NonfairLockWorker worker = new NonfairLockWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}
T[11] lock acquired
T[11] count: 1
T[14] lock acquired
T[14] count: 2
T[14] lock released
T[11] lock released
T[15] lock acquired
T[15] count: 3
T[15] lock released
T[12] lock acquired
T[12] count: 4
T[12] lock released
T[13] lock acquired
T[13] count: 5
T[13] lock released
5

fairLock

Java Lock 建立 5 個執行緒,使用公平鎖,對計數器加 1 ,然後結束任務。

	protected class FairLockWorker implements Runnable {

		private final Lock lock = new ReentrantLock(true);
		private int count;

		public FairLockWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println("T[" + Thread.currentThread().getId() + "] lock acquired");
				count++;
				System.out.println("T[" + Thread.currentThread().getId() + "] count: " + count);
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println("T[" + Thread.currentThread().getId() + "] lock released");
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void fairLock() {
		int expected = 5;
		int taskSize = 5;
		FairLockWorker worker = new FairLockWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}
T[11] lock acquired
T[11] count: 1
T[11] lock released
T[12] lock acquired
T[12] count: 2
T[12] lock released
T[13] lock acquired
T[13] count: 3
T[13] lock released
T[14] lock acquired
T[14] count: 4
T[14] lock released
T[15] lock acquired
T[15] count: 5
T[15] lock released
5

reentrant

Java Lock 建立 1 個執行緒,調用 1 個有加鎖的方法,此方法內又調用另一個有加鎖的方法,等待 1 秒後結束任務。

	protected class ReentrantWorker implements Runnable {

		private final Lock lock = new ReentrantLock();
		private int count;

		public ReentrantWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] run() lock acquired", Thread.currentThread().getId()));
				doCount();
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println(String.format("T[%d] run() lock released", Thread.currentThread().getId()));
			}
		}

		public void doCount() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] doCount() lock acquired", Thread.currentThread().getId()));
				TimeUnit.SECONDS.sleep(1);
				count++;
				System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println(String.format("T[%d] doCount() lock released", Thread.currentThread().getId()));
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void reentrant() {
		int expected = 1;
		int taskSize = 1;
		ReentrantWorker worker = new ReentrantWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}
T[11] run() lock acquired
T[11] doCount() lock acquired
T[11] count: 1
T[11] doCount() lock released
T[11] run() lock released
1

brokenUnlock

Java Lock 建立 1 個執行緒,加鎖對計數器加 1 ,等待 1 秒後結束任務,然後解鎖,再第 2 次解鎖,Lock Java 會拋出例外。

	protected class BrokenUnlockWorker implements Runnable {

		private final Lock lock = new ReentrantLock();
		private int count;

		public BrokenUnlockWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
				TimeUnit.SECONDS.sleep(1);
				count++;
				System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				lock.unlock();
				System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void brokenUnlock() {
		int expected = 1;
		int taskSize = 1;
		BrokenUnlockWorker worker = new BrokenUnlockWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}
T[11] lock acquired
T[11] count: 1
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
	at org.ruoxue.java_147.synchronization.ReentrantLockClassTest$BrokenUnlockWorker.run(ReentrantLockClassTest.java:194)
	at java.lang.Thread.run(Thread.java:750)
1

ReentrantLockClassTest.java

Lock Java 新增單元測試,驗證 ReentrantLock Java 是否符合預期。

package org.ruoxue.java_147.synchronization.reentrantlock;

import static org.junit.Assert.*;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.Test;

public class ReentrantLockClassTest {

	protected class NonfairLockWorker implements Runnable {

		private final Lock lock = new ReentrantLock();
		private int count;

		public NonfairLockWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
				count++;
				System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void nonfairLock() {
		int expected = 5;
		int taskSize = 5;
		NonfairLockWorker worker = new NonfairLockWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}

	protected class FairLockWorker implements Runnable {

		private final Lock lock = new ReentrantLock(true);
		private int count;

		public FairLockWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
				count++;
				System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void fairLock() {
		int expected = 5;
		int taskSize = 5;
		FairLockWorker worker = new FairLockWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}

	protected class ReentrantWorker implements Runnable {

		private final Lock lock = new ReentrantLock();
		private int count;

		public ReentrantWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] run() lock acquired", Thread.currentThread().getId()));
				doCount();
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println(String.format("T[%d] run() lock released", Thread.currentThread().getId()));
			}
		}

		public void doCount() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] doCount() lock acquired", Thread.currentThread().getId()));
				TimeUnit.SECONDS.sleep(1);
				count++;
				System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				System.out.println(String.format("T[%d] doCount() lock released", Thread.currentThread().getId()));
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void reentrant() {
		int expected = 1;
		int taskSize = 1;
		ReentrantWorker worker = new ReentrantWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}

	protected class BrokenUnlockWorker implements Runnable {

		private final Lock lock = new ReentrantLock();
		private int count;

		public BrokenUnlockWorker() {
		}

		@Override
		public void run() {
			lock.lock();
			try {
				System.out.println(String.format("T[%d] lock acquired", Thread.currentThread().getId()));
				TimeUnit.SECONDS.sleep(1);
				count++;
				System.out.println(String.format("T[%d] count: %d", Thread.currentThread().getId(), count));
			} catch (Exception ex) {
				ex.printStackTrace();
			} finally {
				lock.unlock();
				lock.unlock();
				System.out.println(String.format("T[%d] lock released", Thread.currentThread().getId()));
			}
		}

		public int getCount() {
			return count;
		}
	}

	@Test
	public void brokenUnlock() {
		int expected = 1;
		int taskSize = 1;
		BrokenUnlockWorker worker = new BrokenUnlockWorker();
		List<Thread> threads = Stream.generate(() -> new Thread(worker)).limit(taskSize).collect(Collectors.toList());
		threads.forEach(e -> e.start());

		threads.forEach(e -> {
			try {
				e.join();
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		});
		int count = worker.getCount();
		System.out.println(count);
		assertEquals(expected, count);
	}
}

心得分享

ReentrantLock Java Examples 可以分為公平鎖和非公平鎖,區別在於取得鎖的機制上是否公平, Java Lock 是為了保護競爭資源,防止多個執行緒同時操作而出錯,在同一個時間點只能被一個執行緒取得鎖,其它執行緒若同時想要取得鎖,就必須等待, Lock Java 是通過一個 FIFO 的等待佇列來管理取得該鎖的所有執行緒,在公平鎖的機制下,執行緒依次排隊取得鎖,而非公平鎖是在可取得的狀態時,不管自己是不是在佇列的開頭都可以取得鎖。

發佈留言