程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java多線程根底——Lock類

Java多線程根底——Lock類

編輯:關於JAVA

Java多線程根底——Lock類。本站提示廣大學習愛好者:(Java多線程根底——Lock類)文章只能為提供參考,不一定能成為您想要的結果。以下是Java多線程根底——Lock類正文


之前曾經說道,JVM提供了synchronized關鍵字來完成對變量的同步訪問以及用wait和notify來完成線程間通訊。在jdk1.5當前,JAVA提供了Lock類來完成和synchronized一樣的功用,並且還提供了Condition來顯示線程間通訊。
Lock類是Java類來提供的功用,豐厚的api使得Lock類的同步功用比synchronized的同步更弱小。本文章的一切代碼均在Lock類例子的代碼
本文次要引見一下內容:

  1. Lock類
  2. Lock類其他功用
  3. Condition類
  4. Condition類其他功用
  5. 讀寫鎖
Lock類

Lock類實踐上是一個接口,我們在實例化的時分實踐上是實例化完成了該接口的類Lock lock = new ReentrantLock();。用synchronized的時分,synchronized可以修飾辦法,或許對一段代碼塊停止同步處置。
後面講過,針對需求同步處置的代碼設置對象監視器,比整個辦法用synchronized修飾要好。Lock類的用法也是這樣,經過Lock對象lock,用lock.lock來加鎖,用lock.unlock來釋放鎖。在兩者兩頭放置需求同步處置的代碼。
詳細的例子如下:

public class MyConditionService {

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

測試的代碼如下:

        MyConditionService service = new MyConditionService();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();
        new Thread(service::testMethod).start();

        Thread.sleep(1000 * 5);

後果太長就不放出來,詳細可以看我源碼。總之,就是每個線程的打印1-5都是同步停止,順序沒有亂。
經過上面的例子,可以看出Lock對象加鎖的時分也是一個對象鎖,繼續對象監視器的線程才干執行同步代碼,其他線程只能等候該線程釋放對象監視器。

public class MyConditionMoreService {

    private Lock lock = new ReentrantLock();
    public void methodA(){
        try{
            lock.lock();
            System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
            Thread.sleep(1000 * 5);

            System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void methodB(){
        try{
            lock.lock();
            System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
            Thread.sleep(1000 * 5);

            System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() +
                    " time=" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

測試代碼如下:

 public void testMethod() throws Exception {
        MyConditionMoreService service = new MyConditionMoreService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadA aa = new ThreadA(service);
        aa.setName("AA");
        aa.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();

        ThreadB bb = new ThreadB(service);
        bb.setName("BB");
        bb.start();

        Thread.sleep(1000 * 30);
    }
    
public class ThreadA extends Thread{

    private MyConditionMoreService service;

    public ThreadA(MyConditionMoreService service){
        this.service = service;
    }

    @Override
    public void run() {
        service.methodA();
    }
}

public class ThreadB extends Thread{

    private MyConditionMoreService service;

    public ThreadB(MyConditionMoreService service){
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.methodB();
    }
}

後果如下:

methodA begin ThreadName=A time=1485590913520
methodA end ThreadName=A time=1485590918522
methodA begin ThreadName=AA time=1485590918522
methodA end ThreadName=AA time=1485590923525
methodB begin ThreadName=B time=1485590923525
methodB end ThreadName=B time=1485590928528
methodB begin ThreadName=BB time=1485590928529
methodB end ThreadName=BB time=1485590933533

可以看出Lock類加鎖的確是對象鎖。針對同一個lock對象執行的lock.lock是取得對象監視器的線程才干執行同步代碼 其他線程都要等候。
在這個例子中,加鎖,和釋放鎖都是在try-finally。這樣的益處是在任何異常發作的狀況下,都能保證鎖的釋放。

Lock類其他的功用

假如Lock類只要lock和unlock辦法也太復雜了,Lock類提供了豐厚的加鎖的辦法和對加鎖的狀況判別。次要有

  • 完成鎖的公道
  • 獲取以後線程調用lock的次數,也就是獲取以後線程鎖定的個數
  • 獲取等候鎖的線程數
  • 查詢指定的線程能否等候獲取此鎖定
  • 查詢能否有線程等候獲取此鎖定
  • 查詢以後線程能否持有鎖定
  • 判別一個鎖是不是被線程持有
  • 加鎖時假如中綴則不加鎖,進入異常處置
  • 嘗試加鎖,假如該鎖未被其他線程持有的狀況下成功

完成公道鎖

在實例化鎖對象的時分,結構辦法有2個,一個是無參結構辦法,一個是傳入一個boolean變量的結構辦法。當傳入值為true的時分,該鎖為公道鎖。默許不傳參數是非公道鎖。

公道鎖:依照線程加鎖的順序來獲取鎖
非公道鎖:隨機競爭來失掉鎖
此外,JAVA還提供isFair()來判別一個鎖是不是公道鎖。

獲取以後線程鎖定的個數

Java提供了getHoldCount()辦法來獲取以後線程的鎖定個數。所謂鎖定個數就是以後線程調用lock辦法的次數。普通一個辦法只會調用一個lock辦法,但是有能夠在同步代碼中還有調用了別的辦法,那個辦法外部有同步代碼。這樣,getHoldCount()前往值就是大於1。

上面的辦法用來判別等候鎖的狀況

獲取等候鎖的線程數

Java提供了getQueueLength()辦法來失掉等候鎖釋放的線程的個數。

查詢指定的線程能否等候獲取此鎖定

Java提供了hasQueuedThread(Thread thread)查詢該Thread能否等候該lock對象的釋放。

查詢能否有線程等候獲取此鎖定

異樣,Java提供了一個復雜判別能否有線程在等候鎖釋放即hasQueuedThreads()

上面的辦法用來判別持有鎖的狀況

查詢以後線程能否持有鎖定

Java不只提供了判別能否有線程在等候鎖釋放的辦法,還提供了能否以後線程持有鎖即isHeldByCurrentThread(),判別以後線程能否有此鎖定。

判別一個鎖是不是被線程持有

異樣,Java提供了復雜判別一個鎖是不是被一個線程持有,即isLocked()

上面的辦法用來完成多種方式加鎖

加鎖時假如中綴則不加鎖,進入異常處置

Lock類提供了多種選擇的加鎖辦法,lockInterruptibly()也可以完成加鎖,但是當線程被中綴的時分,就會加鎖失敗,停止異常處置階段。普通這種狀況呈現在該線程曾經被打上interrupted的標志了。

嘗試加鎖,假如該鎖未被其他線程持有的狀況下成功

Java提供了tryLock()辦法來停止嘗試加鎖,只要該鎖未被其他線程持有的根底上,才會成功加鎖。

下面引見了Lock類來完成代碼的同步處置,上面引見Condition類來完成wait/notify機制。

Condition類

Condition是Java提供了來完成等候/告訴的類,Condition類還提供比wait/notify更豐厚的功用,Condition對象是由lock對象所創立的。但是同一個鎖可以創立多個Condition的對象,即創立多個對象監視器。這樣的益處就是可以指定喚醒線程。notify喚醒的線程是隨機喚醒一個。
上面,看一個例子,顯示復雜的等候/告訴

public class ConditionWaitNotifyService {

    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();


    public void await(){
        try{
            lock.lock();
            System.out.println("await的時間為 " + System.currentTimeMillis());
            condition.await();
            System.out.println("await完畢的時間" + System.currentTimeMillis());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


    public void signal(){
        try{
            lock.lock();
            System.out.println("sign的時間為" + System.currentTimeMillis());
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}

測試的代碼如下:

        ConditionWaitNotifyService service = new ConditionWaitNotifyService();
        new Thread(service::await).start();
        Thread.sleep(1000 * 3);
        service.signal();
        Thread.sleep(1000);

後果如下:

await的時間為 1485610107421
sign的時間為1485610110423
await完畢的時間1485610110423

condition對象經過lock.newCondition()來創立,用condition.await()來完成讓線程等候,是線程進入阻塞。用condition.signal()來完成喚醒線程。喚醒的線程是用同一個conditon對象調用await()辦法而進入阻塞。並且和wait/notify一樣,await()和signal()也是在同步代碼區內執行。
此外看出await完畢的語句是在獲取告訴之後才執行,的確完成了wait/notify的功用。上面這個例子是展現喚醒制定的線程。

        ConditionAllService service = new ConditionAllService();
        Thread a = new Thread(service::awaitA);
        a.setName("A");
        a.start();

        Thread b = new Thread(service::awaitB);
        b.setName("B");
        b.start();

        Thread.sleep(1000 * 3);

        service.signAAll();

        Thread.sleep(1000 * 4);

後果如下:

begin awaitA時間為 1485611065974ThreadName=A
begin awaitB時間為 1485611065975ThreadName=B
signAll的時間為1485611068979ThreadName=main
end awaitA時間為1485611068979ThreadName=A

該後果的確展現用同一個condition對象來完成等候告訴。
關於等候/告訴機制,簡化而言,就是等候一個條件,當條件不滿足時,就進入等候,等條件滿足時,就告訴等候的線程開端執行。為了完成這種功用,需求停止wait的代碼局部與需求停止告訴的代碼局部必需放在同一個對象監視器外面。執行才干完成多個阻塞的線程同步執行代碼,等候與告訴的線程也是同步停止。關於wait/notify而言,對象監視器與等候條件結合在一同 即synchronized(對象)應用該對象去調用wait以及notify。但是關於Condition類,是對象監視器與條件分開,Lock類來完成對象監視器,condition對象來擔任條件,去調用await以及signal。

Condition類的其他功用

和wait類提供了一個最長等候時間,awaitUntil(Date deadline)在抵達指定時間之後,線程會自動喚醒。但是無論是await或許awaitUntil,當線程中綴時,停止阻塞的線程會發生中綴異常。Java提供了一個awaitUninterruptibly的辦法,使即便線程中綴時,停止阻塞的線程也不會發生中綴異常。

讀寫鎖

Lock類除了提供了ReentrantLock的鎖以外,還提供了ReentrantReadWriteLock的鎖。讀寫鎖分紅兩個鎖,一個鎖是讀鎖,一個鎖是寫鎖。讀鎖與讀鎖之間是共享的,讀鎖與寫鎖之間是互斥的,寫鎖與寫鎖之間也是互斥的。
看上面的讀讀共享的例子:

public class ReadReadService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try{
            try{
                lock.readLock().lock();
                System.out.println("取得讀鎖" + Thread.currentThread().getName() +
                " " + System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.readLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

測試的代碼和後果如下:

        ReadReadService service = new ReadReadService();
        Thread a = new Thread(service::read);
        a.setName("A");

        Thread b = new Thread(service::read);
        b.setName("B");

        a.start();
        b.start();

後果如下:

取得讀鎖A 1485614976979
取得讀鎖B 1485614976981

兩個線程簡直同時執行同步代碼。
上面的例子是寫寫互斥的例子

public class WriteWriteService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void write(){
        try{
            try{
                lock.writeLock().lock();
                System.out.println("取得寫鎖" + Thread.currentThread().getName() +
                        " " +System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.writeLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

測試代碼和後果如下:

        WriteWriteService service = new WriteWriteService();
        Thread a = new Thread(service::write);
        a.setName("A");
        Thread b = new Thread(service::write);
        b.setName("B");

        a.start();
        b.start();
        Thread.sleep(1000 * 30);

後果如下:

取得寫鎖A 1485615316519
取得寫鎖B 1485615326524

兩個線程同步執行代碼

讀寫互斥的例子:

public class WriteReadService {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read(){
        try{
            try{
                lock.readLock().lock();
                System.out.println("取得讀鎖" + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.readLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public void write(){
        try{
            try{
                lock.writeLock().lock();
                System.out.println("取得寫鎖" + Thread.currentThread().getName()
                        + " " + System.currentTimeMillis());
                Thread.sleep(1000 * 10);
            }finally {
                lock.writeLock().unlock();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}

測試的代碼如下:

        WriteReadService service = new WriteReadService();
        Thread a = new Thread(service::write);
        a.setName("A");
        a.start();
        Thread.sleep(1000);

        Thread b = new Thread(service::read);
        b.setName("B");
        b.start();

        Thread.sleep(1000 * 30);

後果如下:

取得寫鎖A 1485615633790
取得讀鎖B 1485615643792

兩個線程讀寫之間也是同步執行代碼。

總結

本文引見了新的同步代碼的方式Lock類以及新的等候/告訴機制的完成Condition類。本文只是很復雜的引見了他們的概念和運用的方式。關於Condition以及讀寫鎖還有更多的內容,將放在當前的博客中。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved