java.util.concurrent.locks與synchronized及其異同
阿新 • • 發佈:2019-02-10
關鍵字:synchronized、java.util.concurrent.locks.Lock、同步、併發、鎖
1、在方法上加synchronized關鍵字
在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執行緒同步小陷阱,你掉進去過嗎?。
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()方法原始碼介紹
a、首先lockInterruptibly呼叫了內部類sync的acquireInterruptibly(1)方法,這個sync就是前面提到的AbstractQueuedSynchronizer的子類
b、在sync的acquireInterruptibly方法中,首先檢查當前現場是否已經中斷,如果已經中斷,丟擲InterruptedException異常,否則呼叫呼叫sync的doAcquireInterruptibly方法。
c、在sync的方法doAcquireInterruptibly中,關鍵在於檢測到中斷則直接退出迴圈(不在等待獲取鎖),而是直接丟擲InterruptedException異常,最後在finally裡呼叫cancelAcquire取消獲鎖操作。
E、除了這些方法之外,ReentrantLock還提供了很多實用的方法,這裡就不再一一講述
對於Lock,還有一個特別值得注意的地方,請看下面的程式碼:
可以看到上面,上面程式碼呼叫lock()方法和呼叫unlock()方法的次數不同,這樣的一段程式碼執行完後,別的執行緒是否已經可以獲取該lock鎖呢?
執行的結果:
永遠都是持有test1這個類的執行緒才能獲取鎖,其實是第一個獲取鎖的執行緒,他永遠都拿著鎖不放。
所以在使用Lock的時候,lock與unlock一定要配對。
3、AbstractQueuedSynchronizer:如果需要自己實現一些特殊規則的鎖,可以通過拓展該類來實現。
參考文件:
http://wenku.baidu.com/view/41480552f01dc281e53af090.html
http://tutorials.jenkov.com/java-concurrency/index.html
一、【引言】
JDK1.5之前,實現同步主要是使用synchronized,而在JDK1.5中新增了java.util.concurrent包及其兩個子包locks和atomic,其中子包locks中定義了系列關於鎖的抽象的類。本文主要介紹java.util.concurrent.locks的使用及其與synchronized兩種方式實現同步的異同。二、【synchronized同步】
synchronized相信很多熟悉J2SE的人都不會對這個關鍵字陌生,它用於實現多個執行緒之間的同步,一般有兩種使用方式:1、在方法上加synchronized關鍵字
2、synchronized同步程式碼塊public synchronized void f() { //do something }
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.util.concurrent.locks下的鎖實現同步】
自JDK1.5以為,Java提供了java.util.concurrent這個併發包,在它的子包locks中,提供了一系列關於鎖的抽象的類。主要有兩種鎖ReentrantLockReentrantReadWriteLock,而其他的兩個類,都是“輔助”類,如AbstractQueuedSynchronizer就是一個用於實現特殊規則鎖的抽象類,ReentrantLock和ReentrantReadWriteLock內部都有一個繼承了該抽象類的內部類,用於實現特定鎖的功能。下文主要介紹:ReentrantLock和ReentrantReadWriteLock1、可重入的鎖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