重入鎖可以替代synchronized關(guān)鍵字,在JDK 5.0的以前版本中,重入鎖的性能遠遠好于synchronized ,從6.0開始,JDK在synchronized上做了大量優(yōu)化,使兩者的性能差別并不大.
public class ReenterLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
lock.lock();
try {
i++;
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws Exception{
ReenterLock rt = new ReenterLock();
Thread t1 = new Thread(rt);
Thread t2 = new Thread(rt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
從代碼中可以看出,重入鎖可以顯示的對代碼塊進行加鎖,釋放鎖,這樣會比synchronized更加靈活,但是需要注意得是,用ReentrantLock 必須手動釋放鎖.
重入鎖的意義: 同一個線程可以兩次獲取同一把鎖,如果不是重入鎖,會產(chǎn)生死鎖的情況.
在以下程序中丘逸,子類改寫了父類的 synchronized 方法挪凑,然后調(diào)用父類中的方法鞭呕,此時如果內(nèi)置鎖不是可重入的乍丈,等待一個永遠等不到的鎖,那么這段代碼將產(chǎn)生死鎖.
public class Widget{
public synchronized void doSomething(){
........
}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
super.doSomething();
}
}
重入鎖的高級功能:
- 中斷響應(yīng)
對于synchronized來說,如果一個線程正在證帶鎖,那么結(jié)果只有兩種情況,要么得到鎖繼續(xù)執(zhí)行,要么它就保持等待. 而使用重入鎖,則提供另外一種可能,那就是線程可以被中斷. 這種情況可以解決一般情況下的死鎖問題.
public class DeadLock implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public DeadLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1){
lock1.lockInterruptibly();
try {
Thread.sleep(500);
}catch (InterruptedException e){}
lock2.lockInterruptibly();
}else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
}catch (InterruptedException e){}
lock1.lockInterruptibly();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (lock1.isHeldByCurrentThread())
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println(Thread.currentThread().getId());
}
}
public static void main(String[] args) throws InterruptedException {
DeadLock deadLock1 = new DeadLock(1);
DeadLock deadLock2 = new DeadLock(2);
Thread t1 = new Thread(deadLock1);
Thread t2 = new Thread(deadLock2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
在上面程序中,如果t1 先占用lock1 在占用lock2,t2 先占用lock2 再占用lock1,很容易發(fā)生 : 在t1去占用lock2時,lock2還沒有被釋放,或者t2去占用lock1時,lock1還沒有被釋放.這就會造成等待現(xiàn)象.但是在程序最后,將t2線程終止,此時t2線程接收到命令后會中斷自己,然后t2釋放鎖,由t1占用,就會解決死鎖問題.
- 限時等待
通常,一個線程拿不到鎖,可能是因為死鎖,也可能是因為饑餓,但是如果給定一個等待時間,讓線程自動放棄,就會解決這個問題.
我們可以用tryLock()方法對線程進行限時等待的限制.
tryLock()方法有兩個參數(shù),第一個是等待時長,第二個是計時單位,如果超過給定時間還沒有得到鎖就會返回false,如果拿到鎖就會返回true. 如以下例子:
public class TimeLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
}else {
System.out.println("get lock failed");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
TimeLock timeLock = new TimeLock();
Thread t1 = new Thread(timeLock);
Thread t2 = new Thread(timeLock);
t1.start();
t2.start();
}
}
當(dāng)t1 或者 t2其中一個線程拿到鎖以后,會占用鎖6秒鐘,所以第二個線程會在五秒內(nèi)嘗試獲得失敗.避免了線程等待.
lock.tryLock()也可以用不傳遞人和參數(shù),代表如果鎖沒有被其他線程占用就會返回true,反之返回false.