java執行緒安全Lock

技術小甜發表於2017-11-09

對於執行緒安全我們前面使用了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.jalja.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.jalja.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秒後的瞬間它就放棄了請求鎖操作。

本文轉自xsster51CTO部落格,原文連結:http://blog.51cto.com/12945177/1950731 ,如需轉載請自行聯絡原作者


相關文章