Java7之多线程读写锁
转载自:http://www.it165.net/pro/html/201402/9242.html
ReadWriteLock是ReentrantReadWriteLock的接口,而ReentrantReadWriteLock实现类中包括子类ReadLock和WriteLock。
首先来看一下ReadWriteLock接口中方法的定义:
1.
public
interface
ReadWriteLock {
2.
Lock readLock();
// 返回用于读取操作的锁
3.
Lock writeLock();
// 返回用于写入操作的锁
4.
}
读取锁和写入锁不可以同时存储,且读取锁可以同时存在多个,但是写入锁只能存在一个。
来看实现类中部分变量和方法,如下:
01.
private
final
ReentrantReadWriteLock.ReadLock readerLock;
// 读锁
02.
private
final
ReentrantReadWriteLock.WriteLock writerLock;
// 写锁
03.
04.
public
ReentrantReadWriteLock.WriteLock writeLock() {
return
writerLock; }
05.
public
ReentrantReadWriteLock.ReadLock readLock() {
return
readerLock; }
06.
07.
// 默认为非公平锁
08.
public
ReentrantReadWriteLock() {
09.
this
(
false
);
10.
}
11.
public
ReentrantReadWriteLock(
boolean
fair) {
12.
sync = fair ?
new
FairSync() :
new
NonfairSync();
13.
readerLock =
new
ReadLock(
this
);
14.
writerLock =
new
WriteLock(
this
);
15.
}
定义了读锁和写锁变量,同时提供了两个构造函数,用来构造公平或非公平锁。
1、获取共享读锁
ReadLock内部类的实现源代码如下:
01.
// 获取读锁,是共享锁
02.
public
static
class
ReadLock
implements
Lock, java.io.Serializable {
03.
private
final
Sync sync;
04.
protected
ReadLock(ReentrantReadWriteLock lock) {
05.
sync = lock.sync;
06.
}
07.
08.
public
void
lock() {
// 共享读锁的获取
09.
sync.acquireShared(
1
);
10.
}
11.
12.
//Acquires the read lock unless the current thread is interrupted.
13.
public
void
lockInterruptibly()
throws
InterruptedException {
14.
sync.acquireSharedInterruptibly(
1
);
15.
}
16.
17.
public
boolean
tryLock() {
18.
return
sync.tryReadLock();
19.
}
20.
21.
public
boolean
tryLock(
long
timeout, TimeUnit unit)
22.
throws
InterruptedException {
23.
return
sync.tryAcquireSharedNanos(
1
, unit.toNanos(timeout));
24.
}
25.
26.
public
void
unlock() {
27.
sync.releaseShared(
1
);
28.
}
29.
public
Condition newCondition() {
30.
throw
new
UnsupportedOperationException();
31.
}
32.
public
String toString() {
33.
int
r = sync.getReadLockCount();
34.
return
super
.toString() +
35.
"[Read locks = "
+ r +
"]"
;
36.
}
37.
}
读取锁是通过调用lock()方法来获取的,在这个方法中调用了acquireShared()方法,这个方法在AQS中实现,如下:
1.
public
final
void
acquireShared(
int
arg) {
2.
if
(tryAcquireShared(arg) <
0
)
3.
doAcquireShared(arg);
4.
}
前面已经多次碰到这相同代码,不解释,直接来看tryAcquireShared()方法的实现:
01.
protected
final
int
tryAcquireShared(
int
unused) {
02.
Thread current = Thread.currentThread();
03.
int
c = getState();
// 获取锁的状态
04.
// 如果锁是互斥锁,并且获取锁的线程不是当前线程,则返回-1,表示获取失败
05.
if
(exclusiveCount(c) !=
0
&& getExclusiveOwnerThread() != current)
06.
return
-
1
;
07.
int
r = sharedCount(c);
// 获取读取锁的共享计数
08.
// 如果不需要阻塞等待,并且读取锁的共享计数小于MAX_COUNT;
09.
// 则通过CAS函数更新锁的状态,将读取锁的共享计数+1。
10.
if
(!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
11.
if
(r ==
0
) {
// 第1次获取读取锁
12.
firstReader = current;
13.
firstReaderHoldCount =
1
;
14.
// 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
15.
}
else
if
(firstReader == current) {
16.
firstReaderHoldCount++;
17.
}
else
{
18.
// HoldCounter是用来统计该线程获取读取锁的次数。
19.
HoldCounter rh = cachedHoldCounter;
20.
if
(rh ==
null
|| rh.tid != current.getId())
21.
cachedHoldCounter = rh = readHolds.get();
22.
else
if
(rh.count ==
0
)
23.
readHolds.set(rh);
24.
rh.count++;
// 将该线程获取读取锁的次数+1
25.
}
26.
return
1
;
27.
}
28.
return
fullTryAcquireShared(current);
29.
}
tryAcquireShared()的作用是尝试获取共享锁,如果通过如上的方法获取失败,则调用fullTryAcquireShared()方法来获取:
01.
final
int
fullTryAcquireShared(Thread current) {
02.
HoldCounter rh =
null
;
03.
for
(;;) {
04.
int
c = getState();
// 获取锁的状态
05.
if
(exclusiveCount(c) !=
0
) {
// 写线程获取互斥锁
06.
if
(getExclusiveOwnerThread() != current)
// 获取锁的线程不是当前线程
07.
return
-
1
;
08.
}
else
if
(readerShouldBlock()) {
// 需要阻塞等待
09.
if
(firstReader == current) {
// 当前线程是第一个线程
10.
}
else
{
11.
if
(rh ==
null
) {
12.
rh = cachedHoldCounter;
13.
if
(rh ==
null
|| rh.tid != current.getId()) {
14.
rh = readHolds.get();
15.
if
(rh.count ==
0
)
16.
readHolds.remove();
17.
}
18.
}
19.
if
(rh.count ==
0
)
// 如果当前线程获取锁的计数为0,则返回-1。
20.
return
-
1
;
21.
}
22.
}
23.
// 不需要阻塞等待,获取读取锁的共享统计数;如果共享统计数超过MAX_COUNT,则抛出异常
24.
if
(sharedCount(c) == MAX_COUNT)
25.
throw
new
Error(
"Maximum lock count exceeded"
);
26.
// 将线程获取读取锁的次数加1。
27.
if
(compareAndSetState(c, c + SHARED_UNIT)) {
28.
// 如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount。
29.
if
(sharedCount(c) ==
0
) {
30.
firstReader = current;
31.
firstReaderHoldCount =
1
;
32.
// 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,
33.
// 则将firstReaderHoldCount+1。
34.
}
else
if
(firstReader == current) {
35.
firstReaderHoldCount++;
36.
}
else
{
37.
if
(rh ==
null
)
38.
rh = cachedHoldCounter;
39.
if
(rh ==
null
|| rh.tid != current.getId())
40.
rh = readHolds.get();
41.
else
if
(rh.count ==
0
)
42.
readHolds.set(rh);
43.
// 更新线程的获取“读取锁”的共享计数
44.
rh.count++;
45.
cachedHoldCounter = rh;
// cache for release
46.
}
47.
return
1
;
48.
}
49.
}
50.
}
doAcquireShared()定义在AQS函数中,源码如下:
01.
private
void
doAcquireShared(
int
arg) {
02.
final
Node node = addWaiter(Node.SHARED);
03.
boolean
failed =
true
;
04.
try
{
05.
boolean
interrupted =
false
;
06.
for
(;;) {
07.
final
Node p = node.predecessor();
08.
if
(p == head) {
09.
int
r = tryAcquireShared(arg);
10.
if
(r >=
0
) {
11.
setHeadAndPropagate(node, r);
12.
p.next =
null
;
// help GC
13.
if
(interrupted)
14.
selfInterrupt();
15.
failed =
false
;
16.
return
;
17.
}
18.
}
19.
if
(shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
20.
interrupted =
true
;
21.
}
22.
}
finally
{
23.
if
(failed)
24.
cancelAcquire(node);
25.
}
26.
}
这段代码与前面的实现相同,在此不估过多的解释。
2、释放共享读锁
调用ReadLock中的unlock()方法来释放共享读锁,在这个方法中调用了如下的方法:
1.
public
final
boolean
releaseShared(
int
arg) {
2.
if
(tryReleaseShared(arg)) {
3.
doReleaseShared();
4.
return
true
;
5.
}
6.
return
false
;
7.
}
01.
protected
final
boolean
tryReleaseShared(
int
unused) {
02.
// 获取当前线程,即释放共享锁的线程
03.
Thread current = Thread.currentThread();
04.
// 如果想要释放锁的线程(current)是第1个获取锁(firstReader)的线程,
05.
// 并且第1个获取锁的线程获取锁的次数=1,则设置firstReader为null;
06.
// 否则,将第1个获取锁的线程的获取次数-1。
07.
if
(firstReader == current) {
08.
// assert firstReaderHoldCount > 0;
09.
if
(firstReaderHoldCount ==
1
)
10.
firstReader =
null
;
11.
else
12.
firstReaderHoldCount--;
13.
}
else
{
14.
// 获取rh对象,并更新当前线程获取锁的信息
15.
HoldCounter rh = cachedHoldCounter;
16.
if
(rh ==
null
|| rh.tid != current.getId())
17.
rh = readHolds.get();
18.
int
count = rh.count;
19.
if
(count <=
1
) {
20.
readHolds.remove();
21.
if
(count <=
0
)
22.
throw
unmatchedUnlockException();
23.
}
24.
--rh.count;
25.
}
26.
for
(;;) {
27.
int
c = getState();
// 获取锁的状态
28.
int
nextc = c - SHARED_UNIT;
// 将锁的获取次数-1
29.
// 通过CAS更新锁的状态
30.
if
(compareAndSetState(c, nextc))
31.
return
nextc ==
0
;
32.
}
33.
}
01.
private
void
doReleaseShared() {
02.
for
(;;) {
03.
// 获取CLH队列的头节点
04.
Node h = head;
05.
// 如果头节点不为null,并且头节点不等于tail节点。
06.
if
(h !=
null
&& h != tail) {
07.
// 获取头节点对应的线程的状态
08.
int
ws = h.waitStatus;
09.
// 如果头节点对应的线程是SIGNAL状态,则意味着“头节点的下一个节点所对应的线程”需要被unpark唤醒。
10.
if
(ws == Node.SIGNAL) {
11.
// 设置“头节点对应的线程状态”为空状态。失败的话,则继续循环。
12.
if
(!compareAndSetWaitStatus(h, Node.SIGNAL,
0
))
13.
continue
;
14.
// 唤醒“头节点的下一个节点所对应的线程”。
15.
unparkSuccessor(h);
16.
}
17.
// 如果头节点对应的线程是空状态,则设置“文件点对应的线程所拥有的共享锁”为其它线程获取锁的空状态。
18.
else
if
(ws ==
0
&&
19.
!compareAndSetWaitStatus(h,
0
, Node.PROPAGATE))
20.
continue
;
// loop on failed CAS
21.
}
22.
// 如果头节点发生变化,则继续循环。否则,退出循环。
23.
if
(h == head)
// loop if head changed
24.
break
;
25.
}
26.
}
3、读取锁的公正锁和非公平锁
公平锁和非公平锁的区别,体现在判断是否需要阻塞的函数readerShouldBlock()是不同的。公平锁的readerShouldBlock()的源码如下:
1.
final
boolean
readerShouldBlock() {
2.
return
hasQueuedPredecessors();
3.
}
在公平共享锁中,如果在当前线程的前面有其他线程在等待获取共享锁,则返回true;否则,返回false。
非公平锁的readerShouldBlock()的源码如下:
1.
final
boolean
readerShouldBlock() {
2.
return
apparentlyFirstQueuedIsExclusive();
3.
}
在非公平共享锁中,它会无视当前线程的前面是否有其他线程在等待获取共享锁。只要该非公平共享锁对应的线程不为null,则返回true
4、举例
01.
public
class
ReadWriteLockTest1 {
02.
03.
public
static
void
main(String[] args) {
04.
// 创建账户
05.
MyCount myCount =
new
MyCount(
"4238920615242830"
,
10000
);
06.
// 创建用户,并指定账户
07.
User user =
new
User(
"Tommy"
, myCount);
08.
// 分别启动3个“读取账户金钱”的线程 和 3个“设置账户金钱”的线程
09.
for
(
int
i=
0
; i<
3
; i++) {
10.
user.getCash();
11.
user.setCash((i+
1
)*
1000
);
12.
}
13.
}
14.
}
15.
16.
class
User {
17.
private
String name;
//用户名
18.
private
MyCount myCount;
//所要操作的账户
19.
private
ReadWriteLock myLock;
//执行操作所需的锁对象
20.
21.
User(String name, MyCount myCount) {
22.
this
.name = name;
23.
this
.myCount = myCount;
24.
this
.myLock =
new
ReentrantReadWriteLock();
25.
}
26.
27.
public
void
getCash() {
28.
new
Thread() {
29.
public
void
run() {
30.
myLock.readLock().lock();
31.
try
{
32.
System.out.println(Thread.currentThread().getName() +
" getCash start"
);
33.
myCount.getCash();
34.
Thread.sleep(
1
);
35.
System.out.println(Thread.currentThread().getName() +
" getCash end"
);
36.
}
catch
(InterruptedException e) {
37.
}
finally
{
38.
myLock.readLock().unlock();
39.
}
40.
}
41.
}.start();
42.
}
43.
44.
public
void
setCash(
final
int
cash) {
45.
new
Thread() {
46.
public
void
run() {
47.
myLock.writeLock().lock();
48.
try
{
49.
System.out.println(Thread.currentThread().getName() +
" setCash start"
);
50.
myCount.setCash(cash);
51.
Thread.sleep(
1
);
52.
System.out.println(Thread.currentThread().getName() +
" setCash end"
);
53.
}
catch
(InterruptedException e) {
54.
}
finally
{
55.
myLock.writeLock().unlock();
56.
}
57.
}
58.
}.start();
59.
}
60.
}
61.
62.
class
MyCount {
63.
private
String id;
//账号
64.
private
int
cash;
//账户余额
65.
66.
MyCount(String id,
int
cash) {
67.
this
.id = id;
68.
this
.cash = cash;
69.
}
70.
71.
public
String getId() {
72.
return
id;
73.
}
74.
75.
public
void
setId(String id) {
76.
this
.id = id;
77.
}
78.
79.
public
int
getCash() {
80.
System.out.println(Thread.currentThread().getName() +
" getCash cash="
+ cash);
81.
return
cash;
82.
}
83.
84.
public
void
setCash(
int
cash) {
85.
System.out.println(Thread.currentThread().getName() +
" setCash cash="
+ cash);
86.
this
.cash = cash;
87.
}
88.
}
运行的结果如下:
01.
Thread-
0
getCash start
02.
Thread-
2
getCash start
03.
Thread-
0
getCash cash=
10000
04.
Thread-
2
getCash cash=
10000
05.
Thread-
0
getCash end
06.
Thread-
2
getCash end
07.
Thread-
1
setCash start
08.
Thread-
1
setCash cash=
1000
09.
Thread-
1
setCash end
10.
Thread-
3
setCash start
11.
Thread-
3
setCash cash=
2000
12.
Thread-
3
setCash end
13.
Thread-
4
getCash start
14.
Thread-
4
getCash cash=
2000
15.
Thread-
4
getCash end
16.
Thread-
5
setCash start
17.
Thread-
5
setCash cash=
3000
18.
Thread-
5
setCash end