(四)Lock,ReentrantLock,ReentrantReadWriteLock類的使用以及相關api---synchronized進階...

這篇博客記錄了Lock,ReentrantLock,ReentrantReadWriteLock類的使用以及其一些api:

碼字不易~~另外《java多線程編程核心技術》這本書讀着很爽

前言說明:之前爲了解決多線程時的非線程安全問題,使用的是synchronized。接下來記錄的是他的升級版本ReentrantLock,更加靈活,可控性更高,而ReentrantReadWriteLock類是對ReentrantLock類的補充,能夠在某些條件之間之下提交效率

下面先來看下都有哪些api,以及和synchronized之間是怎樣對應的吧。

 

以前使用鎖完成同步是將同步代碼塊寫在synchronized之內,現在我們使用  

Lock lock = new ReentrantLock();

來聲明一個鎖,他有這兩個方法

lock.lock();  和 lock.unlock();   這兩個是配套的,在其之間的代碼就是同步代碼塊。

和之前一樣,lock()方法會讓當前線程持有對象監聽器,具體規則之類的和synchronized也一樣,

比如下面的例子,MyService有一段代碼上鎖,自定義線程類調用它

MyService.java

package 第四章;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {
    private Lock lock = new ReentrantLock();
    public void testMethod(){
        lock.lock();
        for(int i=0;i<5;i++){
            System.out.println(i+"線程:"+Thread.currentThread().getName());
        }
        lock.unlock();
    }
}

View Code

MyThread.java

package 第四章;

public class MyThread extends Thread {
    private MyService myService;

    public MyThread(MyService myService) {
        super();
        this.myService = myService;
    }

    public void run(){
        myService.testMethod();
    }
}

View Code

test.java

package 第四章;

public class test {
    public static void main(String[] args){
        MyThread[] threads = new MyThread[5];
        MyService myService = new MyService();
        for(int i=0;i<5;i++){
            threads[i] = new MyThread(myService);
            threads[i].start();
        }
    }
}

View Code

運行結果:

 

 可以看到線程之間是同步執行的,當然前提是同一個MyService對象。

之前的wait/notify,用Condition對象來替換:

效率提高的地方以及原因:

Condition對象可以對同一個鎖聲明多個,相當於每當讓線程等待時,他都有自己的喚醒condition,換句話說,每一個線程都可以註冊一個Condition,這樣當我們喚醒線程的時候,就可以喚醒指定的線程,比如之前的生產者消費者模型之中的假死現象,我們使用過notifyAll()來解決的,但是這種方法喚醒了所有的線程,讓所有線程都去爭搶cpu,但是我們事實上指向喚醒異類線程,並不想喚醒同類,全部喚醒的話,效率是一個問題。那麼現在,給每一個線程都註冊

一個Condition,這樣子喚醒時候,我們就可以喚醒指定的線程,提高了效率,也更加靈活。

下面的是一個簡單的await/signal例子,展示了基本的使用:await類似之前的wait,signal類似於notify:signalAll()喚醒全部

更改之前的MyService.java

condition.await()讓線程阻塞,condition.signal()隨機喚醒一個由當前condition註冊的線程

 

package 第四章;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();
    public void testMethod(){
        try{
        lock.lock();
        System.out.println("即將開始循環");
        condition.await();
        for(int i=0;i<2;i++){
            System.out.println(i+"線程:"+Thread.currentThread().getName());
        }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signal(){
        try{
            lock.lock();
            this.condition.signal();
            System.out.println("喚醒了一個線程");
        }finally {
            lock.unlock();
        }
    }
}

View Code

 

MyThread.java不變

test.java:先讓線程全部阻塞,然後調用自定義的signal方法喚醒線程,

package 第四章;

public class test {
    public static void main(String[] args){
        MyThread[] threads = new MyThread[5];
        MyService myService = new MyService();
        for(int i=0;i<5;i++){
            threads[i] = new MyThread(myService);
            threads[i].start();
        }
        try{
            Thread.sleep(1000);
            myService.signal();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

View Code

運行結果如下:

可以看到,我們成功喚醒了一個線程。

下面的例子喚醒了一個指定的線程

MyService.java:根據當前線程的名字讓指定的Condition對象等待,並書寫兩個喚醒不同的Condition對象註冊的線程

package 第四章;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();
    public void testMethod(){
        try{
            lock.lock();
            System.out.println("線程"+Thread.currentThread().getName()+"等待中...");
            if(Thread.currentThread().getName().equals("A"))
                conditionA.await();
            else
                conditionB.await();
            for(int i=0;i<2;i++){
                System.out.println(i+"線程:"+Thread.currentThread().getName());
        }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signalA(){
        try{
            lock.lock();
            this.conditionA.signal();
            System.out.println("喚醒了A線程");
        }finally {
            lock.unlock();
        }
    }
    public void signalB(){
        try{
            lock.lock();
            this.conditionB.signal();
            System.out.println("喚醒了B線程");
        }finally {
            lock.unlock();
        }
    }
}

View Code

test.java,啓動A,B兩個線程,只喚醒A線程

package 第四章;

public class test {
    public static void main(String[] args){
        MyService myService = new MyService();
        MyThread myThreadA = new MyThread(myService);
        myThreadA.setName("A");
        MyThread myThreadB = new MyThread(myService);
        myThreadB.setName("B");
        myThreadA.start();
        myThreadB.start();
        try{
            Thread.sleep(1000);
            myService.signalA();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

View Code

運行結果:

根據代碼,我們可以看到可以通過不同Condition對象來喚醒指定的線程。

用處:

1.可以想到,如果用Lock來解決之前的多消費多生產者時的假死問題,我們可以將生產者統一註冊一個Condition,消費者統一註冊一個Condition,每一次喚醒對方的Condition,這樣子就不會出現連續喚醒同類導致假死的情況了,並且可以避免喚醒所有線程,導致效率低下。

2.我們也可以按照我們想要的順序進行喚醒,只要你註冊了正確的Condition對象

 

公平鎖和非公平鎖:

比較好理解,公平鎖相當於一個隊列,先進先出,先運行的線程先拿到鎖,後運行的後拿到鎖,按照順序來,非公平鎖就是鎖的搶佔是隨機的,沒有順序。

默認是非公平鎖,創建Lock時加上true參數即爲公平鎖:

Lock lock =new ReentrantLock(true);

 

下面介紹一些ReentrantLock的api,

一般在一些定製化的情況可能會用到,emmm,這塊先了解一下,知道有這些就行,emmm,說實話目前我感覺這個沒啥用,有個印象,不過注意使用這些API使,必須以下面這種方式new對象

ReentrantLock lock = new ReentrantLock();

(lock.)GetHoldCount():查詢當前線程有保持着幾個lock鎖,簡單來講就是當前線程調用了幾次lock()方法

GetQueueLength():有多少個線程在等待獲取當前鎖,可以理解爲有多少個沒有拿到當前鎖,

getWaitQueueLength(Condition condition):有多少個線程處於阻塞狀態,並且是執行了參數Condition對象所對應的await()方法導致阻塞的。

hasQueuedThread(Thread thread):查詢指定的線程是否正在等待獲取當前鎖

hasQueuedThreads():查詢是否有線程正在等待獲取當前鎖

hasWaiters(Condition):查詢是否有線程是由於調用了參數Condition.await()導致阻塞的。

isHeldByCurrentThread():查詢當前線程是否持有當前鎖

isLocked():當前鎖是否被某個線程持有

awaitUninterruptibly():這也是一種讓當前線程阻塞的方法,不過await調用之後如果再使用Interrupt等代碼阻塞當前進程會報異常,但是這個不會,相當於讓當前線程變成可以阻塞的線程,,,,不懂有撒用

awaitUntil(Date):阻塞當前線程,如果在指定時間之前還沒有被喚醒,則喚醒他。參數也可以傳Calendar.getTime(),Calendar類用於處理時間

 

ReentrantReadWriteLock類

之前的ReentrantLock相當於同一時間只有一個線程在執行代碼。但是在不涉及更改實例變量的代碼之中,我們可以允許異步運行來加快效率, 而一些涉及到更改實例變量的代碼,這時候同步執行(這時候異步可能出現非線程安全),這樣可以在一定程度上加快效率,這就是這個類的作用。

簡單來說,我們一般有讀寫兩個操作,如果多個線程執行讀操作,ok,異步執行,如果多個線程有的執行讀,有的寫,ok,同步執行,這個類就是自動完成這個事情,你只需要在鎖時使用不同類型的鎖就行。

下面是一個例子,讀讀異步(其他全部同步):
ReadAndWrite.java  代表具體的操作,讀,寫,輸出當前操作以及時間,sleep()模擬操作耗費的時間

package 第四章;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadAndWrite {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try{
            lock.readLock().lock();
            System.out.println("讀操作"+System.currentTimeMillis());
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
    public void write(){
        try{
            lock.writeLock().lock();
            System.out.println("寫操作"+System.currentTimeMillis());
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}

View Code

MyThread2.java:裏面有兩個java類,一個執行讀操作,一個寫操作

package 第四章;

class MyThreadRead extends Thread{
    private ReadAndWrite readAndWrite;

    public MyThreadRead(ReadAndWrite readAndWrite) {
        this.readAndWrite = readAndWrite;
    }

    public void run(){
        this.readAndWrite.read();
    }
}
class MyThreadWrite extends Thread{
    private ReadAndWrite readAndWrite;

    public MyThreadWrite(ReadAndWrite readAndWrite) {
        this.readAndWrite = readAndWrite;
    }

    public void run(){
        this.readAndWrite.write();
    }
}

View Code

test.java:  創建三個讀線程

package 第四章;

public class test {
    public static void main(String[] args){
           ReadAndWrite readAndWrite = new ReadAndWrite();
           MyThreadRead reads[] = new MyThreadRead[3];
           for(int i=0;i<3;i++) {
               reads[i] = new MyThreadRead(readAndWrite);
               reads[i].start();
           }
        }
    }

View Code

運行結果:

可以看到,三個讀操作時同時執行的。

下面更改test.java,創建三個讀線程,三個寫線程:

test.java

package 第四章;

public class test {
    public static void main(String[] args){

           ReadAndWrite readAndWrite = new ReadAndWrite();
        MyThreadWrite writes[] = new MyThreadWrite[3];
        for(int i=0;i<3;i++) {
            writes[i] = new MyThreadWrite(readAndWrite);
            writes[i].start();
        }
           MyThreadRead reads[] = new MyThreadRead[3];
           for(int i=0;i<3;i++) {
               reads[i] = new MyThreadRead(readAndWrite);
               reads[i].start();
           }

        }
    }

View Code

運行:

 

可以看到,寫操作之間是互斥的,相當於同步,一個一個執行的,讀的時候就是異步的,

 ,,好嘞,就演示這幾個,其他的都同理,只有讀讀是異步的,讀寫同步,你可以交替着start看一下,如下:

 

 好滴,第四章就這些暫時。。

 

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