重入鎖,手動加鎖和釋放鎖,退出臨界區時必須手動釋放,否則其它線程無法通路資源了。
主要有一下幾個特性:
1. 可重入
同一線程可以多次獲得鎖,但是在釋放鎖時,也要釋放相同的次數,如果釋放次數多,會得到一個
IllegalMonitorStateException
異常,如果釋放次數少,相當于還持有鎖。
public class Test01 {
public static class LockTest implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
lock.lock();
lock.lock();
try {
for (int j = 0; j < 10000; j++) {
i++;
}
}finally {
lock.unlock();
lock.unlock();
}
}
}
@Test
public void test01() throws Exception{
LockTest lock1 = new LockTest();
LockTest lock2 = new LockTest();
Thread t1 = new Thread(lock1);
Thread t2 = new Thread(lock2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(LockTest.i);
}
}
2. 可中斷
可中斷 lockInterruptibly()方法,可重入鎖在等待過程中可以被中斷,而synchronized在等待鎖隻有兩種情況,要麼獲得鎖要麼繼續等待。
首先構造一個死鎖,然後中斷其中一個鎖。
public class Test02 {
public static class LockTest implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public LockTest(int lock){
this.lock = lock;
}
@Override
public void run() {
try {
if(lock == 1){
lock1.lockInterruptibly();
Thread.sleep(500);
lock2.lockInterruptibly();
}else {
lock2.lockInterruptibly();
Thread.sleep(500);
lock1.lockInterruptibly();
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()){
lock2.unlock();
}
System.out.println(Thread.currentThread().getId()+"線程退出");
}
}
}
@Test
public void test01() throws Exception{
LockTest r1 = new LockTest(1);
LockTest r2 = new LockTest(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(2000);
t1.interrupt();
}
}
3. 可限時
除了等待外部通知,要避免死鎖的另外一個方法:限時等待。tryLock(),若不加參數運作,鎖被其它線程占用,不會等待,立即傳回
public class Test03 {
public static class LockTest implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(3000, TimeUnit.MILLISECONDS)) {
Thread.sleep(6000);
}else {
System.out.println("get lock failed");
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
@Test
public void test01() throws Exception{
LockTest lockTest = new LockTest();
Thread t1 = new Thread(lockTest);
Thread t2 = new Thread(lockTest);
t1.start();
t2.start();
t1.join();
t2.join();
}
}
4. 公平鎖
大多數情況下,鎖申請是不公平的,可以指定new ReentrantLock(true);實作公平鎖獲得鎖時按照請求鎖的順序。
public class Test04 {
public static class TestLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "獲得鎖");
} finally {
lock.unlock();
}
}
}
}
@Test
public void test01() throws Exception{
TestLock testLock = new TestLock();
Thread t1 = new Thread(testLock, "th1");
Thread t2 = new Thread(testLock, "th2");
Thread t3 = new Thread(testLock, "th3");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
}
5. 重要方法整理
lock()
lockInterruptibly()
tryLock()
6. 重入鎖實作主要三個要素:
- 原子狀态:原子狀态使用cas操作來儲存目前鎖的狀态,判斷鎖是否已經被别的線程持有。
- 等待隊列:所有沒有請求到鎖的線程,會進入等待隊列進行等待。待有線程釋放鎖後,系統就能從等待中喚醒一個線程,繼續工作。
- 阻塞原語park()和unpark():用來挂起和恢複線程。沒有得到鎖的線程将會被挂起。