java併發學習01 --- Reentrantlock 和 Condition

本文是筆者看了《實戰java高併發程序設計》以後加上本身的理解所寫的筆記。java

之因此直接從併發工具開始,是由於多線程的基礎知識,例如多線程建立,經常使用的方法,以及synchronized,volatile關鍵字等知識以前學習的時候已經學習過許多遍了,可是java併發包卻鮮有接觸,此次決定寫成博客,系列的總結一下。(以後有時間的話再把前面的知識補充上來,讓這個系列更完整一些)多線程

 

Reentrantlock(重入鎖) 併發

簡單看一下它的用法:less

重入鎖基本代碼演示:jvm

package thread.thread_util;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 展現重入鎖的用法
 */
public class Lesson16_ReetrantLock {
    public static void main(String[] args) {
        Runnable target = new Runnable() {
            private Lock lock = new ReentrantLock();
            @Override
            public void run() {
                lock.lock();
//                lock.lock();
                try {
                    for (int i = 0; i < 5 ; i++) {
                        System.out.println(Thread.currentThread().getId() + ": " + i);
                    }
                } finally {
                    lock.unlock();
//                    lock.unlock();
                }
            }
        };
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        t1.start();
        t2.start();
    }
}

 

重入究竟是什麼意思?ide

相比synchronized關鍵字,重入鎖顯示的調用了加鎖和解鎖的時機,可是爲何要叫重入鎖呢?重入二字是什麼意思呢?函數

重入的意思就是同一個線程能夠反覆進入同一個鎖,上面的代碼中就算把註釋給去掉代碼也是能夠執行的。高併發

固然這個代碼例子仍然不夠貼切,誰會沒事加兩把鎖上去呢?可是你能夠想象一下遞歸,咱們都知道遞歸的每次操做都把相關的變量壓入了一個棧之中,執行完成就彈出棧,而後再執行上一層函數,這裏若是咱們進行了加鎖操做的話,那麼每一層函數在遞推的時候就都會加上一層鎖,而在迴歸的時候函數結束,則會釋放鎖。重入鎖的內部有一個計數器,每加一個鎖,計數器就加1,釋放一個鎖,計數器就減1,只有當計數器爲0時,鎖才能被釋放。工具

 

咱們以經典的斐波那契數做爲例子來展現一下。學習

深刻理解「重入」代碼演示:

package thread.thread_util;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 遞歸中的重入鎖
 */
public class Lesson16_ReentrantLock02 {
    public static void main(String[] args) {
        Runnable target = new Runnable() {
            private Lock lock = new ReentrantLock();
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId());
                System.out.println("最終結果爲: " + fibonacci(3));
            }

            public int fibonacci (int n) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getId() + "得到了鎖");
                    if( n == 1 ) return 1;
                    if( n == 2 ) return 1;
                    return fibonacci(n-1) + fibonacci(n-2);
                } finally {
                    lock.unlock();
                    System.out.println(Thread.currentThread().getId() + "釋放了鎖");
                }
            }
        };
        Thread thread = new Thread(target);
        thread.start();
    }
}

結果:

11
11得到了鎖
11得到了鎖
11釋放了鎖
11得到了鎖
11釋放了鎖
11釋放了鎖
最終結果爲: 2

咱們能夠看到這裏總共得到了3次鎖,釋放了3次鎖,整個鎖纔會被最終釋放。

你能夠再開一個線程進行驗證,結果會相似下面這樣:

11
11得到了鎖
11得到了鎖
11釋放了鎖
11得到了鎖
11釋放了鎖
11釋放了鎖
最終結果爲: 2
12
12得到了鎖
12得到了鎖
12釋放了鎖
12得到了鎖
12釋放了鎖
12釋放了鎖
最終結果爲: 2

 

重入鎖的好基友:Condition對象

 Condition的功能和Object.wait()與Object.notify()比較類似,它讓咱們能夠控制某個線程何時開始等待,何時被喚醒,而不是由jvm來決定。

 條件對象代碼演示:

package thread.thread_util;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 展現條件對象的基本用法
 * await
 * signal
 * signalAll
 */
public class Lesson17_Condition01 implements Runnable{
    public static Lock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    @Override
    public void run() {
        try {
            lock.lock();
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws Exception{
        Runnable target = new Lesson17_Condition01();
        Thread thread = new Thread(target);
        thread.start();
        Thread.sleep(2000);

        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

這裏在Runnable對象中,將對象鎖住並讓線程進入了休眠狀態

主線程中進行了2秒鐘的休眠,這2秒鐘內子線程也沒法執行,由於被「await()」了,直到2秒鐘後,主線程再次開始執行,對線程進行了喚醒(signal)操做,喚醒操做也要進行加鎖操做。

 

 

關於重入鎖還有幾點須要說明:

  • 中斷響應:對於synchronized來講,線程只有兩種狀態,要麼得到鎖開始執行,要麼在隊列中等待。

          而對於重入鎖來講,還存在另一種狀態,那就是中斷線程,也就是把等待隊列中的線程中斷,那麼這個中斷有什麼用呢?

          好比說若是出現了死鎖的狀況,兩個線程都在等待對方釋放資源,那麼這個時候若是我主動的中斷一個線程,那麼這個線程所持有的資源就被釋放啦。咱們來看看代碼

 重入鎖中斷響應代碼演示:

package thread.thread_util;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 展現重入鎖的中斷響應功能
 * 首先構造一個死鎖
 * 而後中斷某個線程,釋放資源
 * 最後另一個線程就能夠得到釋放的資源,完成線程的任務
 */
public class Lesson16_ReentrantLock03 implements Runnable{
    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();
    private int flag = 0;

    public void setFlag(int val) {
        this.flag = val;
    }
    @Override
    public void run() {

        try {
            if(flag == 1) {
                lock1.lockInterruptibly();
                Thread.sleep(1000);
                lock2.lockInterruptibly();
                System.out.println("Thread1 is going on ");
            }
            else {
                lock2.lockInterruptibly();
                Thread.sleep(1000);
                lock1.lockInterruptibly();
                System.out.println("Thread2 is going on ");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(lock1.isHeldByCurrentThread()){
                lock1.unlock();
            }
            if(lock2.isHeldByCurrentThread()){
                lock2.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Lesson16_ReentrantLock03 target1 = new Lesson16_ReentrantLock03();
        Lesson16_ReentrantLock03 target2 = new Lesson16_ReentrantLock03();

        target1.setFlag(1);
        target2.setFlag(2);

        Thread thread1 = new Thread(target1);
        Thread thread2 = new Thread(target2);
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }
}

 

  • lockInterruptibly  : 雖然鎖住了對象,可是能夠響應中斷,發現中斷以後就釋放鎖

       Acquires the lock unless the current thread is interrupted. (文檔說明)

最後線程2進行了中斷,代碼中的「else」裏面的代碼中的lockInterruptibly()方法就會使線程2 釋放lock2並放棄對lock1的請求。

運行結果:

Thread1 is going on 
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    at thread.thread_util.Lesson16_ReentrantLock03.run(Lesson16_ReentrantLock03.java:33)
    at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

 

 

鎖申請等待時限(tryLock)

這是主動的中斷,還有另一種更爲簡便的方法,就是給鎖的申請添加時限,若是規定時間內我還沒獲得鎖,那我就不要這把鎖了

trylock() 代碼演示:

package thread.thread_util;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Lesson16_ReentrantLock04 implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run () {
        try {
            if(lock.tryLock(3,TimeUnit.SECONDS)){
                System.out.println(Thread.currentThread().getName() + " get lock successful");
                Thread.sleep(4000);
            } else {
                System.out.println(Thread.currentThread().getName() + " get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Lesson16_ReentrantLock04 target = new Lesson16_ReentrantLock04();
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        t1.setName("線程1");
        t2.setName("線程2");
        t1.start();
        t2.start();
    }
}

這裏線程1先得到了鎖,而後睡眠6秒鐘(sleep並不會釋放鎖),因此線程2在3秒鐘內都沒法得到所最後進入else代碼段。

最後結果:

線程1 get lock successful
線程2 get lock failed

 

 

關於重入鎖和條件對象暫時就寫到這裏。

相關文章
相關標籤/搜索