java.util.concurrent.locks
對於線程安全我們前面使用了synchronized關鍵字,對於線程的協作我們使用Object.wait()和Object.notify()。在JDK1.5中java為我們提供了Lock來實現與它們相同的功能,並且性能優於它們,在JDK1.6時,JDK對synchronized做了優化,在性能上兩種方式差距不大了。
一、為什麼出現lock
synchronized修飾的代碼塊,當一個線程獲取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖,如果沒有釋放則需要無限的等待下去。獲取鎖的線程釋放鎖只會有兩種情況:
1、獲取鎖的線程執行完了該代碼塊,然後線程釋放對鎖的占有。
2、線程執行發生異常,此時JVM會讓線程自動釋放鎖。
Lock與synchronized對比:
1、Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問。
2、synchronized不需要手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的占用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。
二、java.util.concurrent.locks包中常用的類和接口。
public interface Lock {
//用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。
void lock();
// 當通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態
void lockInterruptibly() throws InterruptedException;
//它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false
boolean tryLock();
//與tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖
void unlock();
Condition newCondition();
}
1、Lock與unlock
Lock用於獲取鎖,但它不會主動釋放鎖所以需要與unlock()配合使用。一般在使用Lock時必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。
package com.linuxidc.base.threadTest;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable{
public static ReentrantLock lock=new ReentrantLock();
public static int c=0;
public void run() {
for(int i=0;i<1000;i++){
lock.lock();//獲取鎖
try {
System.out.println(Thread.currentThread().getName()+"獲得鎖");
System.out.println(Thread.currentThread().getName()+"====>"+c);
c++;
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println(Thread.currentThread().getName()+"釋放鎖");
lock.unlock();//釋放鎖
}
}
}
public static void main(String[] args) {
LockTest lt=new LockTest();
Thread thread1=new Thread(lt);
Thread thread2=new Thread(lt);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(c);
}
}
注意:同一個線程可以連續獲得同一把鎖,但也必須釋放相同次數的鎖。允許下面的寫法
lock.lock();//獲取鎖
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"獲得鎖");
System.out.println(Thread.currentThread().getName()+"====>"+c);
c++;
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println(Thread.currentThread().getName()+"釋放鎖");
lock.unlock();//釋放鎖
lock.unlock();//釋放鎖
lock.unlock();//釋放鎖
}
2、獲取鎖等待時間tryLock(long time, TimeUnit unit)
如果你約朋友打籃球,約定時間到了你朋友還沒有出現,你等1小時後還是沒到,我想你肯定會掃興的離去。對於線程來說也應該時這樣的,因為通常我們是無法判斷一個線程為什麼會無法獲得鎖,但我們可以給該線程一個獲取鎖的時間限制,如果到時間還沒有獲取到鎖,則放棄獲取鎖。
package com.linuxidc.base.threadTest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockTest implements Runnable{
public static ReentrantLock lock=new ReentrantLock();
private static int m=0;
public void run() {
try {
if(lock.tryLock(1, TimeUnit.SECONDS)){//設置獲取鎖的等待時長1秒
System.out.println(Thread.currentThread().getName()+"獲得鎖");
m++;
//Thread.sleep(2000);//設置休眠2秒
}else{
System.out.println(Thread.currentThread().getName()+"未獲得鎖");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
TryLockTest thread1=new TryLockTest();
TryLockTest thread2=new TryLockTest();
Thread th1=new Thread(thread1);
Thread th2=new Thread(thread2);
th1.start();
th2.start();
try {
//讓main線程等待th1、th2線程執行完畢後,再繼續執行
th1.join();
th2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m);
}
}
執行結果:
Thread-0獲得鎖
Thread-1獲得鎖
2
該代碼就是讓線程在鎖請求中,最多等待1秒,如果超過一秒沒有獲得鎖就返回false,如果獲得了鎖就返回true,根據執行結果可以看出Thread-1線程在1秒內獲得了鎖。
我們開啟注釋 //Thread.sleep(2000);就會發現Thread-1或Thread-0一定會有一個是未獲得鎖,這是因為占用鎖的線程時間是2秒,而等待鎖的線程等待時間是1秒,所以在1秒後的瞬間它就放棄了請求鎖操作。