轉載
locks相關類
鎖相關的類都在包java.util.concurrent.locks
下,有以下類和接口:
|---AbstractOwnableSynchronizer
|---AbstractQueuedLongSynchronizer
|---AbstractQueuedSynchronizer
|---Condition
|---Lock
|---LockSupport
|---ReadWriteLock
|---ReentrantLock
|---ReentrantReadWriteLock
接口摘要:
接口 | 摘要 |
---|---|
Condition | Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,爲每個對象提供多個等待 set(wait-set)。 |
Lock | Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。 |
ReadWriteLock | ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。 |
類摘要:
類 | 摘要 |
---|---|
AbstractOwnableSynchronizer | 可以由線程以獨佔方式擁有的同步器。 |
AbstractQueuedLongSynchronizer | 以 long 形式維護同步狀態的一個 AbstractQueuedSynchronizer 版本。 |
AbstractQueuedSynchronizer | 爲實現依賴於先進先出 (FIFO) 等待隊列的阻塞鎖和相關同步器(信號量、事件,等等)提供一個框架。 |
LockSupport | 用來創建鎖和其他同步類的基本線程阻塞原語。 |
ReentrantLock | 一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。 |
ReentrantReadWriteLock | 支持與 ReentrantLock 類似語義的 ReadWriteLock 實現。 |
ReentrantReadWriteLock.ReadLock | ReentrantReadWriteLock.readLock() 方法返回的鎖。 |
ReentrantReadWriteLock.WriteLock | ReentrantReadWriteLock.writeLock() 方法返回的鎖。 |
synchronized與lock
synchronized對比lock:
1、synchronized是Java語言的關鍵字屬於內置特性,Lock是一個類
2、使用synchronized不需要用戶去手動釋放鎖,使用Lock需要在finally手動釋放鎖,不然容易造成線程死鎖
詳細對比見下面的表格:
類別 | synchronized | Lock |
---|---|---|
存在層次 | Java的關鍵字,在jvm層面上 | 是一個類 |
鎖的釋放 | 1、以獲取鎖的線程執行完同步代碼,釋放鎖 2、線程執行發生異常,jvm會讓線程釋放鎖 | 在finally中必須釋放鎖,不然容易造成線程死鎖 |
鎖的獲取 | 假設A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會一直等待 | 分情況而定,Lock有多個鎖獲取的方式,具體下面會說道,大致就是可以嘗試獲得鎖,線程可以不用一直等待 |
鎖狀態 | 無法判斷 | 可以判斷 |
鎖類型 | 可重入 不可中斷 非公平 | 可重入 可判斷 可公平(兩者皆可) |
性能 | 少量同步 | 大量同步 |
常用類
Lock
Lock是一個接口:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
下面來逐個講述Lock接口中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。unLock()方法是用來釋放鎖的。
lock()
lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。
由於在前面講到如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:
Lock lock = ...;
lock.lock();
try{
//處理任務
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
tryLock()、tryLock(long time, TimeUnit unit)
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
所以,一般情況下通過tryLock來獲取鎖時是這樣使用的:
Lock lock = ...;
if(lock.tryLock()) {
try{
//處理任務
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
}else {
//如果不能獲取鎖,則直接做其他事情
}
lockInterruptibly()
lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。也就是說,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。
因此lockInterruptibly()一般的使用形式如下:
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
}
finally {
lock.unlock();
}
}
注意,當一個線程獲取了鎖之後,是不會被interrupt()方法中斷的。因爲本身在前面的文章中講過單獨調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。
因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。
而用synchronized修飾的話,當一個線程處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。
鎖類型
Java中存在以下幾種鎖:
可重入鎖:在執行對象中所有同步方法不用再次獲得鎖(可看一個使用示例)
可中斷鎖:在等待獲取鎖過程中可中斷
公平鎖: 按等待獲取鎖的線程的等待時間進行獲取,等待時間長的具有優先獲取鎖權利
讀寫鎖:對資源讀取和寫入的時候拆分爲2部分處理,讀的時候可以多線程一起讀,寫的時候必須同步地寫
可重入鎖ReentrantLock
ReentrantLock是唯一實現了Lock接口的類,並且ReentrantLock提供了更多的方法。下面通過一些實例看具體看一下如何使用ReentrantLock。
例子1:lock()的使用
可以類似於Synchronized的用法,定義一個類,新建一個該類的對象用於線程間同步,在類裏面定義鎖的對象。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
new LockTest().init();
}
private void init(){
final Outputer outputer = new Outputer();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("zhangxiaoxiang");
}
}
}).start();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("lihuoming");
}
}
}).start();
}
static class Outputer{
Lock lock = new ReentrantLock();
public void output(String name){
lock.lock();
try{
System.out.println(name);
}finally{
lock.unlock();
}
}
}
}
輸出:
zhangxiaoxiang
lihuoming
lihuoming
zhangxiaoxiang
zhangxiaoxiang
lihuoming
...
注意:輸出的字符串順序不定,個數也不定。
例子2:tryLock()的使用
這裏相比例子1只修改了Outputer類,main方法一樣。
static class Outputer{
Lock lock = new ReentrantLock();
public void output(String name){
if (lock.tryLock()) {
try{
System.out.println(name + "得到鎖");
}finally{
lock.unlock();
System.out.println(name + "釋放鎖");
}
} else {
System.out.println(name + "獲取鎖失敗");
}
}
}
輸出:
lihuoming得到鎖
zhangxiaoxiang獲取鎖失敗
lihuoming釋放鎖
zhangxiaoxiang得到鎖
zhangxiaoxiang釋放鎖
lihuoming得到鎖
lihuoming釋放鎖
...
注意:輸出的字符串順序不定,個數也不定。
例子3:lockInterruptibly()的使用
執行lockInterruptibly()方法的方法中,需要將異常InterruptedException
拋出,在等待鎖的線程可調用interrupt()方法中斷,即可觸發異常InterruptedException
,然後可以在catch中執行相應的操作。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
new LockTest().init();
}
private void init(){
final Outputer outputer = new Outputer();
Thread thread1 = new Thread(new Runnable(){
public void run() {
String name = "zhangxiaoxiang";
try {
Thread.sleep(10);
outputer.output(name);
} catch (InterruptedException e) {
System.out.println(name + "被中斷");
}
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable(){
public void run() {
String name = "lihuoming";
try {
Thread.sleep(10);
outputer.output(name);
} catch (InterruptedException e) {
System.out.println(name + "被中斷");
}
}
});
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
}
static class Outputer{
Lock lock = new ReentrantLock();
//將InterruptedException拋出
public void output(String name) throws InterruptedException {
System.out.println(name + "試圖執行output方法");
lock.lockInterruptibly();
try{
System.out.println(name + "得到鎖");
long startTime = System.currentTimeMillis();
for( ; ;) {
if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
break;
}
}finally{
System.out.println(name + "執行了finally");
lock.unlock();
System.out.println(name + "釋放鎖");
}
}
}
}
輸出:
zhangxiaoxiang試圖執行output方法
zhangxiaoxiang得到鎖
lihuoming試圖執行output方法
lihuoming被中斷
運行之後,發現thread2能夠被正確中斷
在jdk源碼中的一個運用就是類ArrayBlockingQueue
的方法。該方法中有以下幾點注意:
1、使用lock.lockInterruptibly()需拋出異常InterruptedException
2、使用了Condition
3、在finally中關閉鎖
/**
* Inserts the specified element at the tail of this queue, waiting
* up to the specified wait time for space to become available if
* the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
/** notFull是一個Condition對象,
** Condition for waiting puts
** private final Condition notFull;
*/
nanos = notFull.awaitNanos(nanos);
}
insert(e);
return true;
} finally {
lock.unlock();
}
}
讀寫鎖ReadWriteLock
ReadWriteLock也是一個接口,在它裏面只定義了兩個方法:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。ReentrantReadWriteLock實現了ReadWriteLock接口。
ReentrantReadWriteLock裏面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。
讀寫鎖,分爲讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥,由JVM控制。
注意:此鎖最多支持 65535 個遞歸寫入鎖和 65535 個讀取鎖。試圖超出這些限制將導致鎖方法拋出 Error。
下面給出構造函數和常用方法的簡要說明:
類ReentrantReadWriteLock
- ReentrantReadWriteLock(boolean fair): 使用給定的公平策略創建一個新的 ReentrantReadWriteLock。
- ReentrantReadWriteLock():使用默認(非公平)的排序屬性創建一個新的 ReentrantReadWriteLock
類ReentrantReadWriteLock的方法
返回類型 | 方法 |
---|---|
ReentrantReadWriteLock.ReadLock | readLock() 返回用於讀取操作的鎖 |
ReentrantReadWriteLock.WriteLock | writeLock() 返回用於寫入操作的鎖 |
下面給出示例代碼:
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
final Queue3 q3 = new Queue3();
for(int i=0;i<3;i++)
{
final Thread readThread = new Thread() {
public void run() {
while (true) {
q3.get();
}
}
};
readThread.setName("read-"+i);
readThread.start();
final Thread writeThread = new Thread(){
public void run(){
while(true){
q3.put(new Random().nextInt(10000));
}
}
};
writeThread.setName("write-"+i);
writeThread.start();
}
}
}
class Queue3{
private Object data = null;
ReadWriteLock rwl = new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + " have read data :" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
}
public void put(Object data){
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
Thread.sleep((long)(Math.random()*1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
輸出:
read-0 be ready to read data!
read-1 be ready to read data!
read-0 have read data :null
read-1 have read data :null
write-1 be ready to write data!
write-1 have write data: 3713
write-1 be ready to write data!
write-1 have write data: 3420
...
對輸出結果進行分析:be ready to read data!
和have read data
並不是先後出現的,中間可以夾着be ready to read data!
說明讀鎖之間不互斥。
面試題:
緩存系統:取數據,需調用public Object getData(String key)方法,先檢查緩存有沒有這個數據,如果有就直接返回,如果沒有,就從數據庫中查找這個數,然後寫入緩存。
如果使用synchronized對getData加鎖,那麼getData方法只能被一個讀線程執行,其他讀操作就得等待,這裏可以使用一個讀寫鎖,只有在寫的時候才需要互斥
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
public static void main(String[] args) {
// TODO Auto-generated method stub
}
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public Object getData(String key){
rwl.readLock().lock();
Object value = null;
try{
value = cache.get(key);
if(value == null){
rwl.readLock().unlock();
rwl.writeLock().lock();
try{
//再次進行判斷,防止多個寫線程堵在這個地方重複寫
if(value==null){
value = "aaaa"; //設置新值
}
}finally{
rwl.writeLock().unlock();
}
//設置完成 釋放寫鎖,恢復讀寫狀態
rwl.readLock().lock();
}
}finally{
rwl.readLock().unlock();
}
return value;
}
}
其他更多有關ReentrantReadWriteLock後面補充。
Condition
Condition是在java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()這種方式實現線程間協作更加安全和高效。因此通常來說比較推薦使用Condition,,阻塞隊列實際上是使用了Condition來模擬線程間協作。
synchronized常與wait、notify等方法使用,Condition常與await、signal等方法使用。
- Condition是個接口,基本的方法就是await()和signal()方法;
- Condition依賴於Lock接口,生成一個Condition的基本代碼是lock.newCondition()
- 調用Condition的await()和signal()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間纔可以使用
Conditon中的await()對應Object的wait();
Condition中的signal()對應Object的notify();
Condition中的signalAll()對應Object的notifyAll()。
Condition中的long awaitNanos(long nanosTimeout) throws InterruptedException
方法傳入一個等待的微秒時間,該方法返回了所剩毫微秒數的一個估計值,以等待所提供的 nanosTimeout 值的時間,如果超時,則返回一個小於等於 0 的值。可以用此值來確定在等待返回但某一等待條件仍不具備的情況下,是否要再次等待,以及再次等待的時間。此方法的典型用法採用以下形式(上面講ArrayBlockingQueue的public E poll(long timeout, TimeUnit unit)方法中就用到這個方法):
synchronized boolean aMethod(long timeout, TimeUnit unit) {
long nanosTimeout = unit.toNanos(timeout);
while (!conditionBeingWaitedFor) {
if (nanosTimeout > 0)
nanosTimeout = theCondition.awaitNanos(nanosTimeout);
else
return false;
}
// ...
}
代碼示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionCommunication {
public static void main(String[] args) {
final Business business = new Business();
new Thread(
new Runnable() {
public void run() {
for (int i = 1; i <= 50; i++) {
business.sub(i);
}
}
}
).start();
for (int i = 1; i <= 50; i++) {
business.main(i);
}
}
static class Business {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private boolean bShouldSub = true;
public void sub(int i) {
lock.lock();
try {
while (!bShouldSub) {
try {
condition.await();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
condition.signal();
} finally {
lock.unlock();
}
}
public void main(int i) {
lock.lock();
try {
while (bShouldSub) {
try {
condition.await();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
condition.signal();
} finally {
lock.unlock();
}
}
}
}
同樣:await()方法需要放在while循環中。
更多參考:
對比synchronized+notify的使用可以參考:04_張孝祥Java多線程傳統線程同步通信技術
與傳統的同步對比可參考:線程間協作的兩種方式:wait、notify、notifyAll和Condition
參考
詳解synchronized與Lock的區別與使用
張孝祥_Java多線程與併發庫高級應用04
jdk api
線程間協作的兩種方式:wait、notify、notifyAll和Condition