1. 程式人生 > >java高併發實戰程式設計(一)

java高併發實戰程式設計(一)

總覽:什麼是鎖:鎖就是一種資源,用來進行共享資源的保護操作(也可以認為鎖是一條封鎖線,一個物件獲得了可以通過,否則就只能在隔離帶後面等待,除非輪到自己獲取到許可權)。

一.synchronized功能拓展:重入鎖 ReentrantLock

    1.效能和synchronized 相差不大程式碼,程式碼:

        

package com.fortune.util;

import java.util.concurrent.locks.ReentrantLock;

//重入鎖代替synchronized 兩者消耗差不多兩千萬次迴圈
public class ThreadUtils implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i=0;
    @Override
    public void run() {
        for (int j=0;j<10000000;j++){
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }
           /*synchronized (this){
               i++;
           }*/
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Long st = System.currentTimeMillis();
        ThreadUtils tt = new ThreadUtils();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
        System.out.println("消耗時間:"+(System.currentTimeMillis()-st));
    }

    總結:ReentrantLock需要手動加鎖以及釋放鎖(顯示操作),因此靈活度高,但是要在退出臨界區要釋放鎖,否則其它執行緒也進入不了。之所以叫 重入鎖,是因為可以反覆進入,但是僅限於一個執行緒,如上執行緒可以改為:

lock.lock();
lock.lock();
try {
    i++;
}finally {
    lock.unlock();
    lock.unlock();
}

注意:鎖定了幾次就要釋放幾次,獲取以及釋放鎖的消耗時間相比於第一次會大很多。

2.ReentrantLock 可以中斷響應:對於synchronized,如果一個執行緒在等待鎖,要麼獲得鎖繼續執行,要麼保持等待,不能中途退出。而使用重入鎖則可以被中斷

。也就是在等待鎖的過程中,程式可以根據需要取消對鎖的請求。

3.限時鎖:在一定時間內嘗試獲取鎖失敗之後,即返回(不進行任何操作),方法Reentrant().tryLock(),如果不帶引數,當有執行緒獲取到鎖後就直接返回 true,否則立即返回false,不會進行等待,不會產生死鎖。程式碼:

public class ThreadUtils implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();

    public void run() {
        try {
            //在5s內嘗試獲取鎖
            if (lock.tryLock(5, TimeUnit.SECONDS)){
                //這個thread應該是當前執行的執行緒
                System.out.println("我獲取到鎖了");
                Thread.sleep(6000);
                lock.unlock();  
            }else {
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    
    public static void main(String[] args) throws InterruptedException {
       ThreadUtils threadUtils = new ThreadUtils();
       Thread t1 = new Thread(threadUtils);
       Thread t2 = new Thread(threadUtils);
       t1.start();t2.start();
    }

}

4.公平鎖:大多數情況下瑣是不公平的。當執行緒a請求了鎖1,執行緒b也請求了鎖1,當鎖1可用時,到底是先分配a還是b。系統只會從等待佇列中隨機選取一個,不能保證其公平性。而公平鎖就是為了保證先到先得,按照申請的時間順序進行鎖的分配。不會產生飢餓。總會獲取到資源。而synchronized則是非公平鎖,利用Public Reentrant(boolean fair);進行設定。

     根據系統排程:一個執行緒會傾向於再次獲取已經持有的鎖,也就是說一個執行緒第一次獲取了鎖之後,下一次獲取鎖的概率就相對來說大一點。這種方式很高效,但是是不公平的,而公平鎖需要一個有序佇列進行記錄,非常消耗系統資源。

5.重入鎖的好搭檔:Condition條件

     condition物件通過呼叫方法實現和wait()以及notify()方法作用相同。但是 wait()和notify()都是和synchronized 關鍵字合作使用。 而Condition是與重入鎖相關聯的。通過Lock介面的Condition.newCondition()方法生成一個與當前重入鎖繫結的condition例項。利用Condition物件,可以讓執行緒進行等待或者得到通知等。

    方法:

      await() 方法會使當前執行緒等待,同時釋放鎖,當其他執行緒中使用signal()或者signalAll()方法時,執行緒會重新獲得鎖,並繼續執行,或者執行緒被中斷,也能跳出等待和Object.wait()方法相似

     awaitUninterruptibly()方法與 await()方法基本相同,但是不會在等待過程中響應中斷。

     signal()方法用於喚醒一個在等待中的執行緒。相對signalAll()會喚醒所有在等待的執行緒。和Object.notify()方法類似。

  程式碼:

public class ThreadUtils implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    public void run() {
        lock.lock();
        try {
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
       ThreadUtils threadUtils = new ThreadUtils();
       Thread t1 = new Thread(threadUtils);
       t1.start();
       Thread.sleep(7000);
       //通知執行緒t1繼續執行(加鎖是為了防止共享資源衝突,因為執行緒喚醒),因為最開始的鎖已經釋放過了,此時共享資源未獲取鎖。
        lock.lock();
        condition.signal();
       //喚醒一個執行緒,此時需要將共享資源的鎖進行釋放,否則喚醒了也獲取不到鎖,一直保持等待狀態。
        lock.unlock();
    }

}

和Object().wait()和notify()方法一樣,當執行緒使用Condition.await()時,要求執行緒持有相關的重入鎖,(沒獲取鎖進行此操作會報錯)在Condition.await()呼叫後,這個執行緒會釋放這把鎖。同理在Condition.signal()方法呼叫時,也要求執行緒先獲得相關的鎖。在signal()方法呼叫後,系統會從當前Condition物件的等待佇列中,喚醒一個執行緒。一旦執行緒被喚醒,會重新嘗試獲得與之繫結的重入鎖,成功獲取繼續執行,因此在signal()方法呼叫之後,一般需要釋放相關的鎖。謙讓給被喚醒的執行緒,否則喚醒了執行緒但也無法獲得鎖,因此無法真正繼續執行。