Lock鎖的方法使用

1 Lock

通過查看 Lock 的源碼可知,Lock 是一個 接口

public interface Lock {
   void lock();
   void lockInterruptibly() throws InterruptedException;
   boolean tryLock();
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
   void unlock();
   Condition newCondition();
}

下面來逐個講述Lock接口中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。unLock()方法是用來釋放鎖的。newCondition()這個方法暫且不在此講述,會在後面的線程協作一文中講述。

1.1 lock() 方法

lock() 方法是平常使用得最多的一個方法,就是用來獲取鎖如果鎖已被其他線程獲取,則進行等待

如果採用 Lock,必須 主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用 Lock 必須在 try{}catch{}塊 中進行,並且 將釋放鎖的操作放在 finally塊 中進行,以保證鎖一定被被釋放,防止死鎖的發生。
通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //處理任務
}catch(Exception ex){

} finally{
   lock.unlock();   //釋放鎖
}
1.2 tryLock()方法與 tryLock(long time, TimeUnit unit)方法

tryLock() 方法是有返回值的,它表示用來 嘗試獲取鎖,如果獲取 成功,則返回 true,如果獲取 失敗(即鎖已被其他線程獲取),則返回 false,也就說這個方法無論如何都會 立即返回 。在拿不到鎖時不會一直在那等待

tryLock(long time, TimeUnit unit)方法 和 tryLock()方法是類似的,只不過區別在於這個方法 在拿不到鎖時會等待一定的時間 ,在時間期限之內如果還拿不到鎖,就返回 false 。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

一般情況下通過tryLock來獲取鎖時是這樣使用的:

Lock lock = ...;
if(lock.tryLock()) {
   try{
      //處理任務
   }catch(Exception ex){
   
   } finally{
      lock.unlock();   //釋放鎖
   }   
} else {
   //如果不能獲取鎖,則直接做其他事情
}
1.3 lockInterruptibly() 方法

lockInterruptibly() 方法比較特殊,當通過這個方法去獲取鎖時,如果線程 正在等待獲取鎖,則這個線程 能夠響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時通過 lock.lockInterruptibly() 想獲取某個鎖時,假若此時 線程A 獲取到了鎖,而 線程B 只有在等待,那麼對 線程B 調用threadB.interrupt() 方法能夠 中斷線程B的等待過程

由於 lockInterruptibly() 的聲明中拋出了異常,所以lock.lockInterruptibly() 必須放在 try塊 中或者在調用 lockInterruptibly() 的方法外聲明拋出InterruptedException

因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {
   lock.lockInterruptibly();
   try {  
      // ......
   }
   finally {
      lock.unlock();
   }
}

注意,當一個線程獲取了鎖之後,是不會被interrupt()方法中斷的。因爲本身 單獨調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程
因此當通過 lockInterruptibly() 方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的
而用 synchronized 修飾的話,當一個線程處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

2 ReentrantLock

ReentrantLock,意思是“可重入鎖”,ReentrantLock是 唯一實現了Lock接口的類 ,並且ReentrantLock提供了更多的方法。下面通過一些實例看具體看一下如何使用ReentrantLock。

2.1 lock() 的 正確使用方法
public class LockTest {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    
    public static void main(String[] args) {
        final LockTest test = new LockTest();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }

    public void insert(Thread thread){
        Lock lock = new ReentrantLock();  // //注意這個地方
        lock.lock();
        try{
            System.out.println(thread.getName()+"得到了鎖");
            for(int i=0;i<5;i++){
                arrayList.add(i);
            }
        } catch (Exception e){

        }finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}

輸出結果:

Thread-0得到了鎖
Thread-1得到了鎖
Thread-0釋放了鎖
Thread-1釋放了鎖

第二個線程怎麼會在第一個線程釋放鎖之前得到了鎖?原因在於,在 insert 方法中的 lock變量 是局部變量 ,每個線程執行該方法時都會 保存一個副本,那麼理所當然每個線程執行到 lock.lock() 處獲取的是 不同的鎖 ,所以就不會發生衝突。
知道了原因改起來就比較容易了,只需要將lock聲明爲類的屬性即可。

public class LockTest {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意這個地方

    public static void main(String[] args) {
        final LockTest test = new LockTest();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }

    public void insert(Thread thread){
        lock.lock();
        try{
            System.out.println(thread.getName()+"得到了鎖");
            for(int i=0;i<5;i++){
                arrayList.add(i);
            }
        } catch (Exception e){

        }finally {
            System.out.println(thread.getName()+"釋放了鎖");
            lock.unlock();
        }
    }
}

這樣就是正確的方法了

2.2 tryLock()的使用方法
public class LockTest {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意這個地方

    public static void main(String[] args) {
        final LockTest test = new LockTest();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            };
        }.start();

        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }

    public void insert(Thread thread) {
        if (lock.tryLock()) {
            try {
                System.out.println(thread.getName() + "得到了鎖");
                for (int i = 0; i < 5; i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {

            } finally {
                System.out.println(thread.getName() + "釋放了鎖");
                lock.unlock();
            }
        } else{
            System.out.println(thread.getName()+"獲取鎖失敗");
        }
    }
}

輸出結果:

Thread-0得到了鎖
Thread-1獲取鎖失敗
Thread-0釋放了鎖
2.3 lockInterruptibly() 響應中斷的使用方法
public class LockInterruptTest {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockInterruptTest test = new LockInterruptTest();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();

        try{
            Thread.sleep(2000);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        thread2.interrupt();
    }

    public void insert(Thread thread) throws InterruptedException{
        // 注意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然後將InterruptedException拋出
        lock.lockInterruptibly();
        try{
            System.out.println(thread.getName()+"得到了鎖");
            long startTime = System.currentTimeMillis();
            for(; ;){
                if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
            }
        } finally {
            System.out.println(Thread.currentThread().getName()+"執行finally");
            lock.unlock();
            System.out.println(thread.getName()+"釋放了鎖");
        }
    }
}

class MyThread extends Thread {
    private LockInterruptTest test = null;

    public MyThread(LockInterruptTest test){
        this.test = test;
    }
    public void run(){
        try{
            test.insert(Thread.currentThread());
        } catch (InterruptedException e){
            System.out.println(Thread.currentThread().getName()+"被中斷");
        }
    }
}

運行之後,發現thread2能夠被正確中斷。

3 ReadWriteLock

ReadWriteLock也是一個接口,在它裏面只定義了兩個方法:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說 將文件的讀寫操作分開 ,分成2個鎖來分配給線程,從而 使得多個線程可以同時進行讀操作 。下面的ReentrantReadWriteLock實現了ReadWriteLock接口。

4 ReentrantReadWriteLock

ReentrantReadWriteLock裏面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()writeLock() 用來獲取 讀鎖寫鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章