java多線程(二)鎖對象

來源:互聯網
上載者:User

標籤:java   多線程   

轉載請註明出處:http://blog.csdn.net/xingjiarong/article/details/47679007
在上一篇部落格中,我們討論了Race Condition現象以及它產生的原因,現在我們知道它是不好的一種現象了,那麼我們有什麼方法避免它呢。最直接有效方式就是放棄多線程,直接改為使用單線程但操作資料,但是這是不優雅的,因為我們知道有時候,多線程有它自己的優勢。在這裡我們討論兩種其他的方法——鎖對象和條件對象。

鎖對象

java SE5.0之後為實現多線程的互斥引入了ReentrantLock類。ReentrantLock類一個可重新進入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。

ReentrantLock類有兩種構造方法:

構造方法

一、不帶公平參數的構造方法

private ReentrantLock lock = new ReentrantLock();

預設的是非公平鎖,這種鎖不會根據線程等待時間的長短來優先調度線程。

這樣就構造了一個鎖對象lock。

二、帶公平參數的鎖對象

private ReentrantLock lock = new ReentrantLock(true);

此類的構造方法接受一個可選的公平 參數。當設定為 true 時,在多個線程的爭用下,這些鎖傾向於將訪問權授予等待時間最長的線程。否則此鎖將無法保證任何特定訪問順序。

公平鎖和非公平鎖的區別:

與採用預設設定(使用不公平鎖)相比,使用公平鎖的程式在許多線程訪問時表現為很低的總體輸送量(即速度很慢,常常極其慢),但是在獲得鎖和保證鎖分配的均衡性時差異較小。不過要注意的是,公平鎖不能保證線程調度的公平性。因此,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機會,這種情況發生在其他活動線程沒有被處理並且目前並未持有鎖時。

使用方法

class X {    private final ReentrantLock lock = new ReentrantLock();    // 其他變數的定義    public void m() {      lock.lock();  // 當試圖獲得鎖時,如果鎖已經被別的線程佔有,那麼該線程會一直被阻塞,直到獲得鎖     try {       // 處理資料     } finally {       lock.unlock(); //釋放鎖     }   }}

首先為大家介紹一下,ReentrantLock類的兩個最常用的方法:

lock()
獲得鎖對象,如果該鎖對象沒有被其他線程佔有,那麼可以立刻獲得鎖,並執行接下來的處理;如果鎖對象已經被其他對象佔有,那麼該線程就會被阻塞在請求鎖的對象的操作上,直到其他的線程釋放鎖,該線程得到鎖,才能繼續的向下執行。

unlock()
釋放鎖,已經獲得鎖對象的線程在操作完資料後要釋放鎖,以便其他的線程重新獲得鎖來執行自己的操作,否則所有的試圖獲得鎖的線程都不能繼續向下執行。

使用方法簡單明了,就是在執行各個線程都要操作相同資料的代碼之前請求鎖,在finally語句中釋放鎖,為什麼要在finally中釋放鎖呢,這是因為如果try語句塊中有語句發生異常,則會直接跳過try中所有的剩餘程式碼封裝括unlock(),所以鎖對象就不能得到釋放,其他的線程也不能繼續向下執行,導致程式不能繼續執行,我們在finally中釋放鎖,這樣就能保證一定可以將鎖釋放掉,不管獲得鎖的線程是不是正確的執行結束。

現在來使用這個方法修改一下我們上一篇部落格中的代碼:

import java.util.concurrent.locks.ReentrantLock;class MyThread implements Runnable {    /**     * 計算類型,1表示減法,其他的表示加法     */    private int type;    /**     * 鎖對象     */    private static ReentrantLock lock = new ReentrantLock();    public MyThread(int type) {        this.type = type;    }    public void run() {        if (type == 1)            for (int i = 0; i < 10000; i++) {                lock.lock();                Test.num--;                lock.unlock();            }        else            for (int i = 0; i < 10000; i++) {                lock.lock();                Test.num++;                lock.unlock();            }    }}public class Test {    public static int num = 1000000;    public static void main(String[] args) {        Thread a = new Thread(new MyThread(1));        Thread b = new Thread(new MyThread(2));        a.start();        b.start();        /*         * 主線程等待子線程完成,然後再列印數值         */        try {            a.join();            b.join();        } catch (Exception e) {            e.printStackTrace();        }        System.out.println(num);    }}

再多運行幾次,是不是結果都是正確的呢。

現在,我們來解釋一下,什麼是可重新進入的鎖。ReentrantLock類中有一個計數器,用來表示一個線程擷取鎖的數量,初始值為0,當一個線程獲得鎖時,該值被置為1,可重新進入的意思就是,已經獲得鎖的線程還可以繼續調用同一個鎖所保護的方法,也就是再一次獲得鎖,當再一次獲得鎖時,ReentrantLock中的計數器就加1,每釋放一次鎖,計數器就減1,當計數器減為0的時候,這個線程才是真正的釋放了這個鎖。

我們接著討論另外一個非常重要的問題。ReentrantLock類是依賴於建立它的類的對象。什麼意思呢,就是說如果兩個線程同時訪問同一個ReentrantLock對象的lock()方法保護的方法時,OK,這是沒有問題的,鎖對象會成功的保護資料操作不會出錯。但是如果兩個線程同時訪問ReentrantLock類的不同對象的被lock()保護的方法,那麼這兩個線程是不會相互影響的,也就是說lock()方法這時不能保證資料的正確性。

我們來看一下上邊那個代碼的這一部分:

Thread a = new Thread(new MyThread(1));Thread b = new Thread(new MyThread(2));

這裡建立了兩個對象a和b,所以他們每個類都有自己的ReentrantLock對象,這就是我們上邊所說的ReentrantLock類的不同對象,這樣如果兩個線程分別操作a和b的資料,lock方法是不會有效。

不信我們試試看這個代碼:

import java.util.concurrent.locks.ReentrantLock;class MyThread implements Runnable {    /**     * 計算類型,1表示減法,其他的表示加法     */    private int type;    /**     * 鎖對象     */    private ReentrantLock lock = new ReentrantLock();    public MyThread(int type) {        this.type = type;    }    public void run() {        if (type == 1)            for (int i = 0; i < 10000; i++) {                lock.lock();                Test.num--;                lock.unlock();            }        else            for (int i = 0; i < 10000; i++) {                lock.lock();                Test.num++;                lock.unlock();            }    }}public class Test {    public static int num = 1000000;    public static void main(String[] args) {        Thread a = new Thread(new MyThread(1));        Thread b = new Thread(new MyThread(2));        a.start();        b.start();        /*         * 主線程等待子線程完成,然後再列印數值         */        try {            a.join();            b.join();        } catch (Exception e) {            e.printStackTrace();        }        System.out.println(num);    }}

注意這裡的代碼和上邊的代碼並不一樣,唯一的區別就在於聲明ReentrantLock對象時前邊是否加了static,這裡是沒有static修飾,再運行幾次,是不是結果是不正確的呢。

為什麼會這樣呢?因為如果被static修飾,那麼兩個線程就是共用的同一個lock對象,如果不被static修飾,那麼每個線程就是使用的它自己的lock對象,所以不會static修飾就會出現錯誤。

在下一篇部落格裡,我會為大家介紹java條件對象,希望與大家一起學習一起進步,請大家繼續關注我的部落格,如果大家支援我的話,就頂我一下吧。

著作權聲明:本文為博主原創文章,轉載請註明出處,查看原文章,請訪問:http://blog.csdn.net/xingjiarong

java多線程(二)鎖對象

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.