Android小知識-Java多執行緒相關(Lock使用)

顧林海發表於2019-01-07

本篇文章已授權微信公眾號 顧林海 獨家釋出

在JDK1.5中新增ReentrantLock類,效果類似於使用synchronized關鍵字實現執行緒間同步互斥,並且在擴充套件功能上也更加強大,比如具有嗅探鎖定、多路分支通知等功能。看下面ReentrantLock的例項程式碼:

public class Task {

    private Lock mLock=new ReentrantLock();

    public void runTask(){
        mLock.lock();
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        mLock.unlock();
    }

}
複製程式碼

利用ReentrantLock物件的lock()方法獲取鎖,通過unlock()方法釋放鎖。

public class ThreadFirst extends Thread {

    private Task mTask;

    public ThreadFirst(Task task){
        this.mTask=task;
    }

    @Override
    public void run() {
        super.run();
        mTask.runTask();
    }
}
複製程式碼

Client:

public class Client {
    public static void main(String[] args) {
        Task task=new Task();
        ThreadFirst threadFirst;
        for (int i = 0; i < 3; i++) {
            threadFirst=new ThreadFirst(task);
            threadFirst.start();
        }
    }
}
複製程式碼

列印:

Thread-1-->0
Thread-1-->1
Thread-1-->2
Thread-2-->0
Thread-2-->1
Thread-2-->2
Thread-0-->0
Thread-0-->1
Thread-0-->2
複製程式碼

從列印結果看,列印是按分組來列印的,當前執行緒列印完畢後釋放鎖,其他執行緒才可以繼續列印,

在前面文章中可以通過synchronized與wait()和notify()/notifyAll()方法相結合實現等待/通知模式,利用ReentrantLock也可以實現同樣的效果,但需要藉助於Condition物件。Condition可以實現多路通知功能,可以在一個Lock物件裡面建立多個Condition例項,執行緒物件可以註冊在指定的Condition中,從而可以有選擇性進行執行緒通知。

程式碼示例:

public class Task {

    private Lock mLock=new ReentrantLock();
    private Condition mCondition=mLock.newCondition();

    public void runTask(){
        try {
            mLock.lock();
            System.out.println("列印一些資訊:A");
            mCondition.await();
            System.out.println("列印剩餘資訊:B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            mLock.unlock();
            System.out.println("釋放鎖");
        }

    }

}
複製程式碼

執行緒類:

public class ThreadFirst extends Thread {

    private Task mTask;

    public ThreadFirst(Task task){
        this.mTask=task;
    }

    @Override
    public void run() {
        super.run();
        mTask.runTask();
    }
}
複製程式碼

Client:

public class Client {
    public static void main(String[] args) {
        Task task=new Task();
        ThreadFirst threadFirst=new ThreadFirst(task);
        threadFirst.start();
    }
}
複製程式碼

列印:

列印一些資訊:A
複製程式碼

從列印結果看只列印了A,後面B沒列印出來,這是因為呼叫了Condition物件的await()方法,使當前執行任務的執行緒進入等待WAITING狀態。

補齊上面程式碼,在Task類中新新增一個notifyTask方法:

    public void notifyTask(){
        try {
            mLock.lock();
            System.out.println("通知task繼續執行");
            mCondition.signal();
        }finally {
            mLock.unlock();
        }
    }
複製程式碼

Client修改如下:

public class Client {
    public static void main(String[] args) {
        Task task=new Task();
        ThreadFirst threadFirst=new ThreadFirst(task);
        threadFirst.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        task.notifyTask();
    }
}
複製程式碼

列印:

列印一些資訊:A
通知task繼續執行
列印剩餘資訊:B
釋放鎖
複製程式碼

到這裡可以發現Object類的wait()和wait(long)方法相當於Condition類中的await()和await(long)方法。notify()和notifyAll相當於Condition類中的signal()和signalAll()方法。

當然我們也可以建立多個Condition物件來實現通知部分執行緒,程式碼如下:

public class Task {

    private Lock mLock = new ReentrantLock();
    private Condition mConditionA = mLock.newCondition();
    private Condition mConditionB = mLock.newCondition();

    public void runTaskA() {
        try {
            mLock.lock();
            System.out.println("列印一些資訊:A");
            mConditionA.await();
            System.out.println("列印剩餘資訊:A");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
            System.out.println("釋放鎖A");
        }

    }

    public void runTaskB() {
        try {
            mLock.lock();
            System.out.println("列印一些資訊:B");
            mConditionB.await();
            System.out.println("列印剩餘資訊:B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
            System.out.println("釋放鎖B");
        }

    }

    public void notifyTaskA() {
        try {
            mLock.lock();
            System.out.println("通知A繼續執行");
            mConditionA.signal();
        } finally {
            mLock.unlock();
        }
    }

    public void notifyTaskB() {
        try {
            mLock.lock();
            System.out.println("通知B繼續執行");
            mConditionB.signal();
        } finally {
            mLock.unlock();
        }
    }
}
複製程式碼

執行緒類1:

public class ThreadFirst extends Thread {

    private Task mTask;

    public ThreadFirst(Task task){
        this.mTask=task;
    }

    @Override
    public void run() {
        super.run();
        mTask.runTaskA();
    }
}
複製程式碼

執行緒類2:

public class ThreadSecond extends Thread {

    private Task mTask;

    public ThreadSecond(Task task){
        this.mTask=task;
    }

    @Override
    public void run() {
        super.run();
        mTask.runTaskB();
    }
}
複製程式碼

Client:

public class Client {
    public static void main(String[] args) {
        Task task=new Task();
        ThreadFirst threadFirst=new ThreadFirst(task);
        ThreadSecond threadSecond=new ThreadSecond(task);
        threadFirst.start();
        threadSecond.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        task.notifyTaskA();
    }
}
複製程式碼

列印:

列印一些資訊:A
列印一些資訊:B
通知A繼續執行
列印剩餘資訊:A
釋放鎖A
複製程式碼

從列印結果可以看出,使用ReentrantLock物件可以喚醒指定種類的執行緒,非常方便。

鎖Lock分為“公平鎖”和“非公平鎖”,公平鎖表示執行緒獲取鎖的順序是按照執行緒加鎖的順序來分配的,即先到先得的FIFO先進先出順序,而非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得的,和公平鎖不一樣的就是先來的不一定先得到鎖,這樣的話可能造成某些執行緒一直拿不到鎖。


838794-506ddad529df4cd4.webp.jpg

相關文章