重入鎖 ReentrantLock
主要特點:
- 容易控制加鎖的粒度。
lock
unlock
- 等待鎖的過程當中可以響應中斷,
lockInterruptibly
- 可以有限時間內嘗試獲取鎖,
tryLock
- 公平鎖,儘量保證各個線程獲取鎖的公平
- 使用 Condition 對線程進行等待和喚醒
重入鎖的基本使用 lock()
和 unlock()
分別加鎖和釋放鎖,必須一一對應。
class RetrantLockDemo implements Runnable{
private static ReentrantLock lock = new ReentrantLock();
public int i = 0;
@Override
public void run() {
lock.lock(); //加鎖
try {
for (int i = 0; i < 10000; i++) {
this.i++;
}
} finally {
lock.unlock(); //釋放鎖
}
}
public static void main(String[] args) throws InterruptedException {
RetrantLockDemo demo = new RetrantLockDemo();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(demo.i);
}
}
重入鎖可以響應線程的 interupt()
中斷,並且釋放鎖。可響應中斷的鎖需要使用 lockInterruptibly()
class RetrantLockDemo implements Runnable{
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public int i = 0;
public int lock = 1;
public RetrantLockDemo(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (this.lock == 1) {
try {
lock1.lockInterruptibly();
Thread.sleep(1000);
int a = 2;
//這個示例此處會響應中斷,然後當前線程退出, lock()方法不能響應
lock2.lockInterruptibly();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
try {
lock2.lockInterruptibly();
Thread.sleep(1000);
lock1.lockInterruptibly();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} finally {
if (lock1.isHeldByCurrentThread())
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
RetrantLockDemo demo = new RetrantLockDemo(1);
RetrantLockDemo demo2 = new RetrantLockDemo(2);
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo2);
t1.start();
t2.start();
Thread.sleep(2000);
t1.interrupt();
t1.join();
t2.join();
System.out.println(demo.i);
}
}
使用 tryLock(long timeout, TimeUnit unit)
可以嘗試獲取鎖,如果 timeou 時間內獲取不到則越過臨界區,放棄獲取鎖。
使用 tryLock()
嘗試獲取則不等待
class RetrantLockDemo2 implements Runnable{
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
Thread.sleep(6000);
System.out.println("進入線程");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread())
lock.unlock();
}
System.out.println("結束");
}
public static void main(String[] args) throws InterruptedException {
RetrantLockDemo2 demo = new RetrantLockDemo2();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
t1.join();
t2.join();
}
}
通常線程獲取鎖是不公平的,系統調度會傾向於已經獲得鎖的線程,這樣效率更高。
重入鎖提供了公平鎖,new ReentrantLock(true)
,看下面代碼的執行結果基本保證各個線程獲取鎖的公平
class RetrantLockDemo3 implements Runnable{
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 獲得鎖");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread())
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
RetrantLockDemo3 demo = new RetrantLockDemo3();
Thread t1 = new Thread(demo, "線程1");
Thread t2 = new Thread(demo, "線程2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
結果:
線程1 獲得鎖
線程2 獲得鎖
線程1 獲得鎖
線程2 獲得鎖
線程1 獲得鎖
線程2 獲得鎖
線程1 獲得鎖
線程2 獲得鎖
線程1 獲得鎖
線程2 獲得鎖
Condition 提供了重入鎖中的 等待和喚醒功能,示例如下
public class CoditionDemo implements Runnable {
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public int i = 0;
public CoditionDemo() {
}
@Override
public void run() {
try {
try {
lock.lock();
condition.await(); //await 讓當前線程等待,並且會釋放當前的鎖。需要在 lock 和 unlock當中使用,否則會報異常
System.out.println("sinal");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} finally {
if (lock.isHeldByCurrentThread())
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
CoditionDemo demo = new CoditionDemo();
Thread t1 = new Thread(demo);
t1.start();
Thread.sleep(1000);
lock.lock();
//喚醒 condition await 的線程,注意 await 和sinal的前後執行順序。如果 sinal比 await早,那麼就永遠不能喚醒了
//sinal也需要在 lock 和 unlock 當中使用
condition.signalAll();
lock.unlock();
t1.join();
System.out.println(demo.i);
}
}