在java多线程中,为了提高效率有些共享资源允许同时进行多个读的操作,但只允许一个写的操作,比如一个文件,只要其内容不变可以让多个线程同时读,不必做排他的锁定,排他的锁定只有在写的时候需要,以保证别的线程不会看到数据不完整的文件。
下面是个关于多线程读写锁的例子,我稍微做了下修改,蛮容易理解的,来至于http://www.highya.com/redirect.php?fid=113&tid=7180&goto=nextoldset。
这里模拟了这样一个场景: 在ReadWriteLockOperator对象里设置一个共享资源 shareResources 。
有3个读者(A, B, C)一直连续的从 shareResources 获取信息, 然后输出到控制台 ;有一个作者每隔60秒往shareResources 加入信息, 加信息的过程相对耗时, 在这段时间, 任何读者都不能访问 shareResources。
写了4个类来验证这种情况,只在windows下做了测试。
ReadTask.java 读任务
WriteTask.java 写任务
ReadWriteLockLogic.java 读写操作的逻辑
ReadWriteLockTest.java 带有main方法的测试类
---------------------------------------混哥线-----------------------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
ReadTask
extends
Thread {
//logic bean
private
ReadWriteLockLogic readWriteLockOperator;
//读者
private
String reader;
public
ReadTask(ReadWriteLockLogic readWriteLockOperator, String reader) {
this
.readWriteLockOperator = readWriteLockOperator;
this
.reader = reader;
}
private
ReadTask(){}
// 执行任务
public
void
run() {
if
(
this
.readWriteLockOperator !=
null
){
try
{
while
(!isInterrupted()){
Thread.sleep(
200
);
System.out.println(reader +
" read:"
+ Thread.currentThread().toString() +
" : "
+
this
.readWriteLockOperator.read());
}
}
catch
(Exception e) {
// TODO: handle exception
}
}
}
}
|
-------------------------------------------------------------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
WriteTask
extends
Thread{
//logic bean
private
ReadWriteLockLogic readWriteLockOperator;
//作者
private
String writer;
public
WriteTask(ReadWriteLockLogic readWriteLockOperator, String writer) {
this
.readWriteLockOperator = readWriteLockOperator;
this
.writer = writer;
}
private
WriteTask(){}
// 一个很耗时的写任务
public
void
run() {
try
{
while
(!isInterrupted()){
Thread.sleep(
100
);
this
.readWriteLockOperator.write(
this
.writer,
"hehehhe"
);
}
}
catch
(Exception e) {
// TODO: handle exception
}
}
}
|
----------------------------------------------------------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
import
java.util.ArrayList;
import
java.util.List;
import
java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReadWriteLock;
import
java.util.concurrent.locks.ReentrantReadWriteLock;
//读写操作的逻辑
public
class
ReadWriteLockLogic {
// 初始化一个 ReadWriteLock
private
ReadWriteLock lock =
new
ReentrantReadWriteLock();
//共享资源
private
List<String> shareResources =
new
ArrayList<String>(
0
);
//读
public
String read() {
// 得到 readLock 并锁定
Lock readLock = lock.readLock();
readLock.lock();
try
{
// 读相对省时,做空循环 大约0.5second
for
(
int
i=
0
;i<
2500000
; i++){
System.out.print(
""
);
}
// 做读的工作
StringBuffer buffer =
new
StringBuffer();
for
(String shareResource : shareResources) {
buffer.append(shareResource);
buffer.append(
"\t"
);
}
return
buffer.toString();
}
finally
{
readLock.unlock();
//一定要保证锁的释放
}
}
//写
public
void
write(String writer, String content) {
// 得到 writeLock 并锁定
Lock writeLock = lock.writeLock();
writeLock.lock();
try
{
System.out.println(writer +
" write ==="
+ Thread.currentThread().toString());
// 写比较费时,所以做空循环 大约13second
for
(
int
i=
0
;i<
10000000
; i++){
System.out.print(
""
);
System.out.print(
""
);
}
// 做写的工作
int
count = shareResources.size();
for
(
int
i=count; i < count +
1
; i++) {
shareResources.add(content +
"_"
+ i);
}
}
finally
{
writeLock.unlock();
//一定要保证锁的释放
}
}
}
|
------------------------------------------------------------------------------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import
java.util.concurrent.ExecutionException;
import
java.util.concurrent.Executors;
import
java.util.concurrent.ScheduledExecutorService;
import
java.util.concurrent.TimeUnit;
public
class
ReadWriteLockTest {
public
static
void
main(String[] args)
throws
InterruptedException, ExecutionException {
//1 创建一个具有排程功能的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(
5
);
//2 读写锁的logic bean
ReadWriteLockLogic lockOperator =
new
ReadWriteLockLogic();
//3 生成一个可执行任务(该任务执行完毕可以返回结果 或者 抛出异常;而Runnable接口的run方法则不行)
Runnable writeTask1 =
new
WriteTask(lockOperator,
"作者A"
);
//4 延时0秒后每2秒重复执行writeTask1;
service.scheduleAtFixedRate(writeTask1,
0
,
60
, TimeUnit.SECONDS);
//5 创建3个读任务
Runnable readTask1 =
new
WriteTask(lockOperator,
"作者B"
);
Runnable readTask2 =
new
ReadTask(lockOperator,
"读者B"
);
Runnable readTask3 =
new
ReadTask(lockOperator,
"读者C"
);
//6 延时0秒后每秒执行一次task1;
service.scheduleAtFixedRate(readTask1,
1
,
1
, TimeUnit.SECONDS);
service.scheduleAtFixedRate(readTask2,
2
,
1
, TimeUnit.SECONDS);
service.scheduleAtFixedRate(readTask3,
3
,
1
, TimeUnit.SECONDS);
}
}
|
----------------------------------------------------------------------------------------
作者A write ===Thread[pool-1-thread-1,5,main]
作者B write ===Thread[pool-1-thread-4,5,main]
读者C read:Thread[pool-1-thread-3,5,main] : hehehhe_0 hehehhe_1
读者B read:Thread[pool-1-thread-2,5,main] : hehehhe_0 hehehhe_1
作者A write ===Thread[pool-1-thread-1,5,main]
................
通过观察控制台,可以看到作者a出现后,大约5秒作者b才会出现,而又过了5秒后,读者c和读者b同时会出现,接着5秒后,作者a又出现了。这说明了,读锁之间没有排斥,可以多线程持有并且排斥WriteLock的持有线程。而WriteLock是全部排斥的,是独占的,比较独!
下面是附赠的读写锁的小知识,来至http://www.txdnet.cn/essay/view.jsp?tid=1288670091703&cid=2
(a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想.
(b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有.反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a),呵呵.
(c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥.这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量.
(d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致.
(e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常.