在java中,除了用synchronized控制線程之外,還可以用ReentLock來控制。
synchronized簡(jiǎn)單方便,但是適用場(chǎng)景比較單一,局限比較多。ReentrantLock則靈活得多,具有時(shí)間鎖等待、可中斷鎖等功能。
synchronized是JVM實(shí)現(xiàn)的,而Lock是代碼層面實(shí)現(xiàn)的。
synchronized封裝了線程信息,便于調(diào)試,而Lock則并不知道自己當(dāng)前所在線程是哪一個(gè)。
需要注意的是,synchronized自己實(shí)現(xiàn)了鎖的釋放,所以我們執(zhí)行完操作時(shí),并不需要特意去釋放鎖,而Lock并沒有,所以我們需要在拿到鎖之后,需要在finally中執(zhí)行鎖的釋放,如果忘記了,很容易出現(xiàn)問題。
另外,從性能方面來說,高并發(fā)的情況,Lock的性能好過synchronized。
簡(jiǎn)單介紹下ReentrantLock的使用
lock
嘗試獲取鎖,如果拿不到,則一直等待,效果等同于synchronized
Lock lock = new ReentrantLock();
//創(chuàng)建一個(gè)重入鎖
lock.lock();
//申請(qǐng)鎖,此步驟往下都是鎖住范圍
try{
//拿到鎖了,執(zhí)行操作
}finally{
lock.unlock();
//釋放鎖,在執(zhí)行完操作之后,手動(dòng)釋放鎖
}
tryLock
嘗試獲取鎖,如果拿不到,則不等待鎖的釋放,直接執(zhí)行另一操作
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// 拿到鎖了,執(zhí)行操作
} finally {
lock.unlock();
}
} else {
// 沒拿到鎖,執(zhí)行操作
}
tryLock還有一個(gè)重載方法,可以傳入等待時(shí)間
Lock lock = new ReentrantLock();
if (lock(5, TimeUnit.SECONDS)) {
//試圖在五秒內(nèi)獲取鎖,若獲取不到,返回false
try {
// 拿到鎖了,執(zhí)行操作
} finally {
lock.unlock();
}
} else {
// 沒拿到鎖,執(zhí)行操作
}
lockInterruptibly
在synchronized中,如果申請(qǐng)的對(duì)象鎖被其他線程獲取了,那么此線程會(huì)一直等待直到對(duì)象鎖被釋放,并且無法終止等待。那么在Lock中,lockInterruptibly方法表示,申請(qǐng)鎖,并且可以中斷申請(qǐng)。
public class SleepThread extends Thread{
private ReentrantLock lock;
public SleepThread(ReentrantLock lock){
this.lock=lock;
}
@Override
public void run() {
super.run();
lock.lock();
try {
sleep(100000);
//拿到鎖之后睡覺
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
public class InterruptiblyThread extends Thread{
private ReentrantLock lock;
public InterruptiblyThread(ReentrantLock lock){
this.lock=lock;
}
@Override
public void run() {
super.run();
try {
lock.lockInterruptibly();
//申請(qǐng)鎖,此過程可以被中斷
try{
}finally{
lock.unlock();
}
} catch (InterruptedException e) {
//阻塞被喚醒并被要求處理中斷
//抓住或者拋出中斷
System.out.println("我被中斷啦");
}
}
}
public static void main(String[] args) {
ReentrantLock lock=new ReentrantLock();
Thread sleep=new SleepThread(lock);
Thread inter=new InterruptiblyThread(lock);
sleep.start();
inter.start();
inter.interrupt();
}
我被中斷啦
等待與喚醒
在synchronized中,線程通信用Object的wait、notify、notifyAll方法來實(shí)現(xiàn)。并且對(duì)象鎖的喚醒與等待,是一一對(duì)應(yīng)的。這種情況下,如果我只想喚醒其中某部分線程,那就必須用到多個(gè)對(duì)象鎖了。
Lock的本身實(shí)現(xiàn)了線程的通信方法,通過Condition的await、signal、signalAll來分別對(duì)應(yīng)Object的方法。并且一個(gè)Lock可以和多個(gè)Condition關(guān)聯(lián)。
舉個(gè)栗子:
ReentrantLock lock = new ReentrantLock(true);
Condition empty = lock.newCondition();
Condition full = lock.newCondition();
public void lockConsume() {
// 如果隊(duì)列空了,則消費(fèi)者等待
if (TaskQueue.getInstance().isEmpty()) {
lock.lock();
try {
if (TaskQueue.getInstance().isEmpty()) {
// 生產(chǎn)者喚醒,這里只能喚醒由full.await進(jìn)入等待的線程
full.signal();
try {
//消費(fèi)者等待
empty.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
} else {
//消費(fèi)
TaskQueue.getInstance().getTask();
}
}
private void lockProduce() {
// 如果隊(duì)列滿了,生產(chǎn)者暫停
if (TaskQueue.getInstance().isFull()) {
lock.lock();
try {
if (TaskQueue.getInstance().isFull()) {
// 消費(fèi)者喚醒,這里同樣只能喚醒由empty.await進(jìn)入等待的線程
empty.signal();
try {
//生產(chǎn)者暫停
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
} else {
//生產(chǎn)
Bean b = new Bean();
TaskQueue.getInstance().addTask(b);
}
}
我用兩個(gè)Condition控制了多個(gè)線程,而Lock卻只有一個(gè)。Condition的方法和Object并沒有什么功能上的不同,只不過更加靈活。
公平性(FIFO)
大家可能注意到上面等待與喚醒中舉的栗子,初始化Lock的時(shí)候和之前的栗子不同,傳入了一個(gè)布爾參數(shù),這個(gè)參數(shù)就是用來控制Lock的公平性,即是否先入先出(默認(rèn)為false)。
如果Lock是公平的,那么線程執(zhí)行的順序會(huì)嚴(yán)格按照請(qǐng)求鎖的順序依次獲得鎖,而如果不是,則線程可能比其他先請(qǐng)求鎖的線程先得到鎖。
Condition和Lock是綁定的,所以Condition也有Lock的公平性,Condition按照await的順序一一喚醒。這里Condition按照自己的await隊(duì)列喚醒之后,添加到Lock申請(qǐng)鎖的隊(duì)列中,依然按照FIFO獲得鎖。
從性能上來說,公平性比非公平性的性能低,因?yàn)镴VM無法根據(jù)條件調(diào)度線程,只能嚴(yán)格執(zhí)行隊(duì)列順序。