推薦:Java并發程式設計彙總
Java并發程式設計一ReentrantReadWriteLock初使用
ReentrantReadWriteLock
是一種讀寫鎖,從類名也可以看出來。
ReentrantReadWriteLock
類有兩個屬性
ReentrantReadWriteLock.ReadLock readerLock
(代表讀鎖)、
ReentrantReadWriteLock.WriteLock writerLock
(代表寫鎖)。
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
ReadLock
類和
WriteLock
類都是
ReentrantReadWriteLock
類的内部類,它們都實作了
Lock
接口。
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}
public int getHoldCount() {
return sync.getWriteHoldCount();
}
}
我們來示範一下怎麼使用
ReentrantReadWriteLock
。
測試代碼:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了讀鎖,正在讀取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放讀鎖");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了寫鎖,正在寫入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放寫鎖");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->read(),"Thread1").start();
new Thread(()->read(),"Thread2").start();
new Thread(()->write(),"Thread3").start();
new Thread(()->write(),"Thread4").start();
}
}
輸出:
Thread1得到了讀鎖,正在讀取
Thread2得到了讀鎖,正在讀取
Thread2釋放讀鎖
Thread1釋放讀鎖
Thread3得到了寫鎖,正在寫入
Thread3釋放寫鎖
Thread4得到了寫鎖,正在寫入
Thread4釋放寫鎖
很明顯可以看出,讀鎖是可以被多個線程同時持有的,而寫鎖卻不行,這也很正常,讀資料并不會修改資料,是以多個線程一起持有讀鎖也是線程安全的,而寫資料就不一樣了。
規則如下:
- 線程持有讀鎖,其他線程想要擷取讀鎖是可行的。
- 線程持有讀鎖,其他線程想要擷取寫鎖是不可行的。
- 線程持有寫鎖,其他線程想要擷取讀鎖是不可行的。
- 線程持有寫鎖,其他線程想要擷取寫鎖是不可行的。
讀寫鎖的方法就不示範了,看下面這篇部落格即可。
Java并發程式設計一Lock和ReentrantLock初使用
fair屬性
設定
fair
屬性為
true
,則表明為公平鎖,設定為
false
,則表明為非公平鎖。
首先來看看設定
fair
屬性為
true
的情況。
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
fair
屬性為
true
,
sync
會被指派為
new FairSync()
。
/**
* Fair version of Sync
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
看看
hasQueuedPredecessors()
的源碼。
/**
* Queries whether any threads have been waiting to acquire longer
* than the current thread.
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
從代碼與注釋中,可以很明顯看出,如果有其他線程排在目前線程前面,目前線程就要等待前面的線程先擷取鎖,讀寫鎖都是如此,這種擷取鎖的順序,可見是公平的,先來先得。
再來看看設定
fair
屬性為
false
的情況。
fair
屬性為
false
,
sync
會被指派為
new NonfairSync()
。
/**
* Nonfair version of Sync
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
writerShouldBlock()
一直會傳回為
false
,可見想要擷取寫鎖的目前線程不需要等待前面的線程先擷取鎖,它是可以去搶鎖的。
/**
* Returns {@code true} if the apparent first queued thread, if one
* exists, is waiting in exclusive mode. If this method returns
* {@code true}, and the current thread is attempting to acquire in
* shared mode (that is, this method is invoked from {@link
* #tryAcquireShared}) then it is guaranteed that the current thread
* is not the first queued thread. Used only as a heuristic in
* ReentrantReadWriteLock.
*/
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
isShared()
是用來判斷該結點是不是在等待共享鎖,也就是讀鎖,是以從上面的代碼我們可以知道,如果第一個結點是想要擷取寫鎖的,那麼想要擷取讀鎖的目前線程是不能搶的,否則是可以搶的。
鎖降級
寫鎖是可以降級為讀鎖的,而讀鎖是不可以更新為寫鎖的
(認為寫鎖比讀鎖級别更高)。
測試代碼:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Upgrading {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(
false);
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void readUpgrading() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了讀鎖,正在讀取");
Thread.sleep(1000);
System.out.println("更新會帶來阻塞");
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "擷取到了寫鎖,更新成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放寫鎖");
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "釋放讀鎖");
readLock.unlock();
}
}
private static void writeDowngrading() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了寫鎖,正在寫入");
Thread.sleep(1000);
readLock.lock();
System.out.println("在不釋放寫鎖的情況下,直接擷取讀鎖,成功降級");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放讀鎖");
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "釋放寫鎖");
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("示範降級是可行的");
Thread thread1 = new Thread(() -> writeDowngrading(), "Thread1");
thread1.start();
thread1.join();
System.out.println("------------------");
System.out.println("示範更新是不行的");
Thread thread2 = new Thread(() -> readUpgrading(), "Thread2");
thread2.start();
}
}
輸出:
示範降級是可行的
Thread1得到了寫鎖,正在寫入
在不釋放寫鎖的情況下,直接擷取讀鎖,成功降級
Thread1釋放讀鎖
Thread1釋放寫鎖
------------------
示範更新是不行的
Thread2得到了讀鎖,正在讀取
更新會帶來阻塞
線程在鎖更新過程中被一直阻塞着,這也很明顯驗證了
寫鎖是可以降級為讀鎖的,而讀鎖是不可以更新為寫鎖的
。為什麼不可以呢?這樣設計是有原因的,因為鎖更新容易引起死鎖。