java.util.concurrent.locks與synchronized及其異同

來源:互聯網
上載者:User
文章目錄
  • 一、【引言】
  • 二、【synchronized同步】
  • 三、【java.util.concurrent.locks下的鎖實現同步】

關鍵字:synchronized、java.util.concurrent.locks.Lock、同步、並發、鎖
一、【引言】

JDK1.5之前,實現同步主要是使用synchronized,而在JDK1.5中新增了java.util.concurrent包及其兩個子包locks和atomic,其中子包locks中定義了系列關於鎖的抽象的類。本文主要介紹java.util.concurrent.locks的使用及其與synchronized兩種方式實現同步的異同。
二、【synchronized同步】

synchronized相信很多熟悉J2SE的人都不會對這個關鍵字陌生,它用於實現多個線程之間的同步,一般有兩種使用方式:
1、在方法上加synchronized關鍵字

public synchronized void f() {//do something}

2、synchronized同步代碼塊

synchronized (mutex) {// do something}

對於這兩種方式又應該著重區分是否為“static”的,因為static的成員是屬於類的,非staitc的是屬於具體執行個體的,所以在使用synchronized時應該注意方法或選擇的同步變數是否為static的,如下代碼:

package test.lock;/** * @author whwang * 2012-1-10 下午11:19:04 */public class SyncTest {        private Object mutex = new Object();        public synchronized void f1() {        synchronized (mutex) {            System.err.println("nonstatic method f1....");            try {                Thread.sleep(2 * 1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }        public static void main(String[] args) {        SycnThread thread1 = new SycnThread(new SyncTest());        SycnThread thread2 = new SycnThread(new SyncTest());        SycnThread thread3 = new SycnThread(new SyncTest());        thread1.start();        thread2.start();        thread3.start();    }    }class SycnThread extends Thread {        private SyncTest st;        public SycnThread(SyncTest st) {        this.st = st;    }        @Override    public void run() {        while (true) {            st.f1();        }    }}

在main方法,建立thread1、2、3三個線程,它們都調用SyncTest的f1()方法,而方法f1()使用mutex(非static)來做同步變數,如果你的意圖是實現這3個線程對方法f1的同步,那麼啟動並執行結果會讓你大失所望,因為這樣根本就無法使得這3個線程同步,原因在於:mutex是一個非static的成員變數,也就是說每new SyncTest(),它們的mutex變數都是不相同的。這樣,對於上面這個程式來說,意味著每一個線程都使用了一個mutex來做它們各自的不同變數,如果希望上面3個線程同步,可以把mutex改為static或在建立SycnThread時,傳入的SyncTest都為同一個對象即可。
還有當使用String常量或全域變數時都應該引起注意,Java線程同步小陷阱,你掉進去過嗎?。
三、【java.util.concurrent.locks下的鎖實現同步】

自JDK1.5以為,Java提供了java.util.concurrent這個並發包,在它的子包locks中,提供了一系列關於鎖的抽象的類。主要有兩種鎖ReentrantLockReentrantReadWriteLock,而其他的兩個類,都是“輔助”類,如AbstractQueuedSynchronizer就是一個用於實現特殊規則鎖的抽象類別,ReentrantLock和ReentrantReadWriteLock內部都有一個繼承了該抽象類別的內部類,用於實現特定鎖的功能。下文主要介紹:ReentrantLock和ReentrantReadWriteLock
1、可重新進入的鎖ReentrantLock

使用ReentrantLock鎖最簡單的一個例子:

Lock lock = new ReentrantLock();try {    lock.lcok();    // do something} finally {    lock.unlock();}

上面這段代碼,首先建立了一個lock,然後調用它的lock()方法,開啟鎖定,在最後調用它的unlock()解除鎖定。值得注意的時,一般在使用鎖時,都應該按上面的風格書寫代碼,即lock.unlock()最好放在finally塊,這樣可以防止,執行do something時發生異常後,導致鎖永遠無法被釋放。

到此,還沒發現Lock與synchronized有什麼不同,Lock與synchronized不同之處主要體現在Lock比synchronized更靈活得多,而這些靈活又主要體現在如下的幾個方法:

//lock()
tryLock()
tryLock(long timeout, TimeUnit timeUnit)
lockInterruptibly()

//unlock()

A、trylock()方法:如果擷取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;
B、tryLock(long timeout, TimeUnit timeUnit)方法:如果擷取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果擷取了鎖定,就返回true,如果等待逾時,返回false;
是不是比synchronized靈活就體現出來了,打個不是很恰當的比分:你現在正在忙於工作,突然感覺內急,於是你跑向洗手間,到門口發現一個“清潔中,暫停使用”的牌牌。沒辦法,工作又忙,所以你只好先放棄去洗手間回去忙工作,可能如此反覆,終於你發現可以進了,於是......
像這樣的情境用synchronized你怎麼實現?沒辦法,如果synchronized,當你發現洗手間無法暫時無法進入時,就只能乖乖在門口乾等了。而使用trylock()呢,首先你試著去洗手間,發現暫時無法進入(trylock返回false),於是你繼續忙你的工作,如此反覆,直到可以進入洗手間為止(trylock返回true)。甚至,你非常急,你可以嘗試性的在門口等20秒,不行再去忙工作(trylock(20, TimeUnit.SECONDS);)。
C、lockInterruptibly()方法
lockInterruptibly()方法的執行如下:

如果該鎖定沒有被另一個線程保持,則擷取該鎖定並立即返回,將鎖定的保持計數設定為 1。
如果當前線程已經保持此鎖定,則將保持計數加 1,並且該方法立即返回。
如果鎖定被另一個線程保持,則出於線程調度目的,禁用當前線程,並且在發生以下兩種情況之一以前,該線程將一直處於休眠狀態:
    a、鎖定由當前線程獲得;
    b、或者其他某個線程中斷當前線程。
如果當前線程獲得該鎖定,則將鎖定保持計數設定為1。
如果當前線程:
    a、在進入此方法時已經設定了該線程的中斷狀態;
    b、或者在等待擷取鎖定的同時被中斷。
則拋出 InterruptedException,並且清除當前線程的已中止狀態。
即lockInterruptibly()方法允許在等待時由其它線程調用它的Thread.interrupt方法來中斷等待而直接返回,這時不再擷取鎖,而會拋出一個InterruptedException。
D、lockInterruptibly()方法源碼介紹

public void lockInterruptibly() throws InterruptedException {    sync.acquireInterruptibly(1);}

a、首先lockInterruptibly調用了內部類sync的acquireInterruptibly(1)方法,這個sync就是前面提到的AbstractQueuedSynchronizer的子類

abstract static class Sync extends AbstractQueuedSynchronizer {    // ....}
public final void acquireInterruptibly(int arg)            throws InterruptedException {    if (Thread.interrupted())        throw new InterruptedException();    if (!tryAcquire(arg))        doAcquireInterruptibly(arg);}

b、在sync的acquireInterruptibly方法中,首先檢查當前現場是否已經中斷,如果已經中斷,拋出InterruptedException異常,否則調用調用sync的doAcquireInterruptibly方法。

private void doAcquireInterruptibly(int arg)        throws InterruptedException {        final Node node = addWaiter(Node.EXCLUSIVE);        boolean failed = true;        try {            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return;                }        // 拋出InterruptedException異常                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    throw new InterruptedException();            }        } finally {            if (failed)                cancelAcquire(node);        }    }

c、在sync的方法doAcquireInterruptibly中,關鍵在於檢測到中斷則直接退出迴圈(不在等待擷取鎖),而是直接拋出InterruptedException異常,最後在finally裡調用cancelAcquire取消獲鎖操作。
E、除了這些方法之外,ReentrantLock還提供了很多實用的方法,這裡就不再一一講述
對於Lock,還有一個特別值得注意的地方,請看下面的代碼:

Lock lock = new ReentrantLock();// ....try {    lock.lock();    lock.lock();    // do something...} finally {    lock.unlock();}// ....

可以看到上面,上面代碼調用lock()方法和調用unlock()方法的次數不同,這樣的一段代碼執行完後,別的線程是否已經可以擷取該lock鎖呢?

package test.mult;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName: Test * @author whwang * @date 2012-1-11 下午02:04:04 */public class Test {    private String name;        public Test(String name) {        this.name = name;    }        public static void main(String[] args) {        // 這樣建立一個"公平競爭"的鎖        Lock lock = new ReentrantLock(true);        MyThread t1 = new MyThread(lock, new Test("test1"));        MyThread t2 = new MyThread(lock, new Test("test2"));        t1.start();        t2.start();    }    private static class MyThread extends Thread {        Lock lock = null;        Test test = null;        public MyThread(Lock lock, Test test) {            this.lock = lock;            this.test = test;        }        @Override        public void run() {            while (true) {                try {                    // 調用兩次lock                    lock.lock();                    lock.lock();                    System.err.println(test.name + " locked...");                    try {                        Thread.sleep(3 * 1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                } finally {                    lock.unlock();                }            }        }    }}

啟動並執行結果:

test1 locked...test1 locked...test1 locked...test1 locked...test1 locked...test1 locked...

永遠都是持有test1這個類的線程才能擷取鎖,其實是第一個擷取鎖的線程,他永遠都拿著鎖不放。
所以在使用Lock的時候,lock與unlock一定要配對

2、可重新進入的讀寫鎖ReentrantReadWriteLock

該鎖的用法與ReentrantLock基本一樣,只是ReentrantReadWriteLock實現了特殊規則(讀寫鎖),在ReentrantReadWriteLock中有兩個內部類ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock(實際上不止兩個內部類,還有實現AbstractQueuedSynchronizer的Sync等等),這兩個類分別可以使用ReentrantReadWriteLock的readLock()和writeLock()返回,該讀寫鎖的規則是:只要沒有writer,讀取鎖定可以由多個reader
線程同時保持,而寫入鎖定是獨佔的。下面通過一個簡單的例子來瞭解它:

package test.mult;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** @ClassName: ReadWriteLockTest* @author whwang* @date 2012-1-11 下午02:20:59 */public class ReadWriteLockTest {    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);    public static void main(String[] args) {        // 是否可以進入多個reader - 可以        // 是否可以進入多個writer - 不可以        // 當有reader進入後, writer是否可以進入 - 不可以        // 當有writer進入後, reader是否可以進入 - 不可以        MyThread t1 = new MyThread(0, "t1");        MyThread t2 = new MyThread(0, "t2");        MyThread t3 = new MyThread(1, "t3");        t1.start();        t2.start();        t3.start();    }    private static class MyThread extends Thread {        private int type;        private String threadName;        public MyThread(int type, String threadName) {            this.threadName = threadName;            this.type = type;        }        @Override        public void run() {            while (true) {                if (type == 0) {                    // read                    ReentrantReadWriteLock.ReadLock readLock = null;                    try {                        readLock = lock.readLock();                        readLock.lock();                        System.err.println("to read...." + threadName);                        try {                            Thread.sleep(5 * 1000);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    } finally {                        readLock.unlock();                    }                } else {                    // write                    ReentrantReadWriteLock.WriteLock writeLock = null;                    try {                        writeLock = lock.writeLock();                        writeLock.lock();                        System.err.println("to write...." + threadName);                        try {                            Thread.sleep(5 * 1000);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    } finally {                        writeLock.unlock();                    }                }            }        }    }}

3、AbstractQueuedSynchronizer:如果需要自己實現一些特殊規則的鎖,可以通過拓展該類來實現。

參考文檔:

http://wenku.baidu.com/view/41480552f01dc281e53af090.html

http://tutorials.jenkov.com/java-concurrency/index.html

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.