深入研究ReentrantLock(重入鎖)
阿新 • • 發佈:2019-02-07
synchronized原語和ReentrantLock在一般情況下沒有什麼區別,但是在非常複雜的同步應用中,請考慮使用ReentrantLock,特別是遇到下面2種需求的時候。
1.某個執行緒在等待一個鎖的控制權的這段時間需要中斷
2.需要分開處理一些wait-notify,ReentrantLock裡面的Condition應用,能夠控制notify哪個執行緒
3.具有公平鎖功能,每個到來的執行緒都將排隊等候
下面細細道來……
先說第一種情況,ReentrantLock的lock機制有2種,忽略中斷鎖和響應中斷鎖,這給我們帶來了很大的靈活性。比如:如果A、B2個執行緒去競爭鎖,A執行緒得到了鎖,B執行緒等待,但是A執行緒這個時候實在有太多事情要處理,就是一直不返回,B執行緒可能就會等不及了,想中斷自己,不再等待這個鎖了,轉而處理其他事情。這個時候ReentrantLock就提供了2種機制,第一,B執行緒中斷自己(或者別的執行緒中斷它),但是ReentrantLock不去響應,繼續讓B執行緒等待,你再怎麼中斷,我全當耳邊風(synchronized原語就是如此);第二,B執行緒中斷自己(或者別的執行緒中斷它),ReentrantLock處理了這個中斷,並且不再等待這個鎖的到來,完全放棄。(如果你沒有了解java的中斷機制,請參考下相關資料,再回頭看這篇文章,80%的人根本沒有真正理解什麼是java的中斷,呵呵)
這裡來做個試驗,首先搞一個Buffer類,它有讀操作和寫操作,為了不讀到髒資料,寫和讀都需要加鎖,我們先用synchronized原語來加鎖,如下:
Java程式碼
public class Buffer {
private Object lock;
public Buffer() {
lock = this;
}
public void write() {
synchronized (lock) {
long startTime = System.currentTimeMillis();
System.out.println("開始往這個buff寫入資料…");
for (;;)// 模擬要處理很長時間
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE)
break;
}
System.out.println("終於寫完了");
}
}
public void read() {
synchronized (lock) {
System.out.println("從這個buff讀資料");
}
}
}
接著,我們來定義2個執行緒,一個執行緒去寫,一個執行緒去讀。
Java程式碼
public class Writer extends Thread {
private Buffer buff;
public Writer(Buffer buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
public class Reader extends Thread {
private Buffer buff;
public Reader(Buffer buff) {
this.buff = buff;
}
@Override
public void run() {
buff.read();//這裡估計會一直阻塞
System.out.println("讀結束");
}
}
好了,寫一個Main來試驗下,我們有意先去“寫”,然後讓“讀”等待,“寫”的時間是無窮的,就看“讀”能不能放棄了。
Java程式碼
public class Test {
public static void main(String[] args) {
Buffer buff = new Buffer();
final Writer writer = new Writer(buff);
final Reader reader = new Reader(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
//等5秒鐘去中斷讀
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,嘗試中斷");
reader.interrupt();
break;
}
}
}
}).start();
}
}
我們期待“讀”這個執行緒能退出等待鎖,可是事與願違,一旦讀這個執行緒發現自己得不到鎖,就一直開始等待了,就算它等死,也得不到鎖,因為寫執行緒要21億秒才能完成 T_T ,即使我們中斷它,它都不來響應下,看來真的要等死了。這個時候,ReentrantLock給了一種機制讓我們來響應中斷,讓“讀”能伸能屈,勇敢放棄對這個鎖的等待。我們來改寫Buffer這個類,就叫BufferInterruptibly吧,可中斷快取。
Java程式碼
import java.util.concurrent.locks.ReentrantLock;
public class BufferInterruptibly {
private ReentrantLock lock = new ReentrantLock();
public void write() {
lock.lock();
try {
long startTime = System.currentTimeMillis();
System.out.println("開始往這個buff寫入資料…");
for (;;)// 模擬要處理很長時間
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE)
break;
}
System.out.println("終於寫完了");
} finally {
lock.unlock();
}
}
public void read() throws InterruptedException {
lock.lockInterruptibly();// 注意這裡,可以響應中斷
try {
System.out.println("從這個buff讀資料");
} finally {
lock.unlock();
}
}
}
當然,要對reader和writer做響應的修改
Java程式碼
public class Reader extends Thread {
private BufferInterruptibly buff;
public Reader(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
try {
buff.read();//可以收到中斷的異常,從而有效退出
} catch (InterruptedException e) {
System.out.println("我不讀了");
}
System.out.println("讀結束");
}
}
/**
* Writer倒不用怎麼改動
*/
public class Writer extends Thread {
private BufferInterruptibly buff;
public Writer(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
public class Test {
public static void main(String[] args) {
BufferInterruptibly buff = new BufferInterruptibly();
final Writer writer = new Writer(buff);
final Reader reader = new Reader(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,嘗試中斷");
reader.interrupt();
break;
}
}
}
}).start();
}
}
1.某個執行緒在等待一個鎖的控制權的這段時間需要中斷
2.需要分開處理一些wait-notify,ReentrantLock裡面的Condition應用,能夠控制notify哪個執行緒
3.具有公平鎖功能,每個到來的執行緒都將排隊等候
下面細細道來……
先說第一種情況,ReentrantLock的lock機制有2種,忽略中斷鎖和響應中斷鎖,這給我們帶來了很大的靈活性。比如:如果A、B2個執行緒去競爭鎖,A執行緒得到了鎖,B執行緒等待,但是A執行緒這個時候實在有太多事情要處理,就是一直不返回,B執行緒可能就會等不及了,想中斷自己,不再等待這個鎖了,轉而處理其他事情。這個時候ReentrantLock就提供了2種機制,第一,B執行緒中斷自己(或者別的執行緒中斷它),但是ReentrantLock不去響應,繼續讓B執行緒等待,你再怎麼中斷,我全當耳邊風(synchronized原語就是如此);第二,B執行緒中斷自己(或者別的執行緒中斷它),ReentrantLock處理了這個中斷,並且不再等待這個鎖的到來,完全放棄。(如果你沒有了解java的中斷機制,請參考下相關資料,再回頭看這篇文章,80%的人根本沒有真正理解什麼是java的中斷,呵呵)
這裡來做個試驗,首先搞一個Buffer類,它有讀操作和寫操作,為了不讀到髒資料,寫和讀都需要加鎖,我們先用synchronized原語來加鎖,如下:
Java程式碼
public class Buffer {
private Object lock;
public Buffer() {
lock = this;
}
public void write() {
synchronized (lock) {
long startTime = System.currentTimeMillis();
System.out.println("開始往這個buff寫入資料…");
for (;;)// 模擬要處理很長時間
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE)
break;
}
System.out.println("終於寫完了");
}
}
public void read() {
synchronized (lock) {
System.out.println("從這個buff讀資料");
}
}
}
接著,我們來定義2個執行緒,一個執行緒去寫,一個執行緒去讀。
Java程式碼
public class Writer extends Thread {
private Buffer buff;
public Writer(Buffer buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
public class Reader extends Thread {
private Buffer buff;
public Reader(Buffer buff) {
this.buff = buff;
}
@Override
public void run() {
buff.read();//這裡估計會一直阻塞
System.out.println("讀結束");
}
}
好了,寫一個Main來試驗下,我們有意先去“寫”,然後讓“讀”等待,“寫”的時間是無窮的,就看“讀”能不能放棄了。
Java程式碼
public class Test {
public static void main(String[] args) {
Buffer buff = new Buffer();
final Writer writer = new Writer(buff);
final Reader reader = new Reader(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
//等5秒鐘去中斷讀
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,嘗試中斷");
reader.interrupt();
break;
}
}
}
}).start();
}
}
我們期待“讀”這個執行緒能退出等待鎖,可是事與願違,一旦讀這個執行緒發現自己得不到鎖,就一直開始等待了,就算它等死,也得不到鎖,因為寫執行緒要21億秒才能完成 T_T ,即使我們中斷它,它都不來響應下,看來真的要等死了。這個時候,ReentrantLock給了一種機制讓我們來響應中斷,讓“讀”能伸能屈,勇敢放棄對這個鎖的等待。我們來改寫Buffer這個類,就叫BufferInterruptibly吧,可中斷快取。
Java程式碼
import java.util.concurrent.locks.ReentrantLock;
public class BufferInterruptibly {
private ReentrantLock lock = new ReentrantLock();
public void write() {
lock.lock();
try {
long startTime = System.currentTimeMillis();
System.out.println("開始往這個buff寫入資料…");
for (;;)// 模擬要處理很長時間
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE)
break;
}
System.out.println("終於寫完了");
} finally {
lock.unlock();
}
}
public void read() throws InterruptedException {
lock.lockInterruptibly();// 注意這裡,可以響應中斷
try {
System.out.println("從這個buff讀資料");
} finally {
lock.unlock();
}
}
}
當然,要對reader和writer做響應的修改
Java程式碼
public class Reader extends Thread {
private BufferInterruptibly buff;
public Reader(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
try {
buff.read();//可以收到中斷的異常,從而有效退出
} catch (InterruptedException e) {
System.out.println("我不讀了");
}
System.out.println("讀結束");
}
}
/**
* Writer倒不用怎麼改動
*/
public class Writer extends Thread {
private BufferInterruptibly buff;
public Writer(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
public class Test {
public static void main(String[] args) {
BufferInterruptibly buff = new BufferInterruptibly();
final Writer writer = new Writer(buff);
final Reader reader = new Reader(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,嘗試中斷");
reader.interrupt();
break;
}
}
}
}).start();
}
}