0
点赞
收藏
分享

微信扫一扫

ReentrantReadWriteLock读写锁的使用1


本文可作为传智播客《张孝祥-Java多线程与并发库高级应用》的学习笔记。

一个简单的例子

两个线程,一个不断打印a,一个不断打印b



public class LockTest {
public static void main(String[] args){
final Outputer outputer = new Outputer();
new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}

}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
}//a的数量与b的数量一致

}
}).start();

}

static class Outputer{
public void output(String name){
int len = name.length();
try{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}finally{

}
}
}
}



最后的部分结果


bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb


aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


b


bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb


aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


为什么会这样?


很简单,在输出b的时候,还没有输出完,a线程(打印a的那个线程)已经抢到了控制权,开始打印a,等a线程将a输出完后,并且打印了一个回车后,b线程才抢回系统控制权,打印它上一次最后剩下的一个b。


要解决上面的问题很简单:


static class Outputer{
public synchronized void output(String name){
int len = name.length();
//.....
}
}

这样一来,我们就保证了Outputer类里的output方法是原子性的,不会有两个线程同时执行它。



就上面的例子而言我们是否还有更好的方法呢?


有。


java5中提供了一种更加面向对象的技术类解决多线程之间的互斥问题-----锁。


java.util.concurrent.locks Interface Lock


锁技术的核心就是Lock及它的实现类。



基本锁


*******************************************


*******************************************


以下为2016您3月21日补充




既然都说到锁了,我们就看上java.util.concurrent下都有什么东西


首先concurrent下有两个子包



ReentrantReadWriteLock读写锁的使用1_lock



atomic与locks


atomic包里面主要是对基本数据类型如int,float,boolean等的原子封装


lock包是我们今天要说的



ReentrantReadWriteLock读写锁的使用1_多线程_02



OK有3个接口


首先我先说明,这3个接口之间并没有继承的关系


Lock与ReadWriteLock都是锁,可以实现线程的互斥,只是ReadWriteLock可以更进一步的实现读与读不互斥(更多的资料,见下文)



ReentrantReadWriteLock读写锁的使用1_读写锁_03




ReentrantReadWriteLock读写锁的使用1_读写锁_04



上面的readlock与writelock分别是ReentrantReadWriteLock的两个静态内部类


Condition呢,上面的Lock实现了线程的互斥,但是我们还得实现线程的通信呀,那就是condition


ReadWriteLock没有这个方法的


关于condition,可参见拙作


​​聊聊condition​​







以上为2016您3月21日补充


*******************************************


*******************************************

上面的例子如果使用锁,代码如下


static class Outputer{
Lock lock = new ReentrantLock();

public void output(String name){
int len = name.length();
lock.lock(); //标识1
try{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}finally{
lock.unlock(); //标识2
}
}

线程a执行到上面代码的标识1处加锁,当线程a在输出字符a时,线程b也执行到了标识1处。此时线程b是不能获得锁的。它被阻塞到标识1处,直到线程a打印完之后在标识2处释放了锁。(线程a线程b共用一把锁,也就是Lock lock = new ReentrantLock())


另外为什么标识2出的释放锁放到了finally里,大家应该明白了吧。


读与写

上面的问题中output的主体(len是方法内部的局部变量,为每个线程自有,互不干涉)被全部互斥,它保证了任何时候,都只有一个线程执行标识1与标识2直接的代码。

但是我们得意识到:对共有数据的操作,基本可以分为两类,读与写。


对共有资源操作的时候,我们应该遵循三大准则:


1 当一个线程对资源进行写操作的时候,别的线程既不能对资源读也不能对资源写。

2 当一个线程对资源进行读操作的时候,别的线程不能对资源写。

3 当一个线程对资源进行读操作的时候,别的线程能对资源读。


一二准则保证了系统的正确性。第三准则能提高系统的性能。 毕竟多个线程对资源进行读操作是可以的。


看下面这个既有读又有写的例子。


public class ReadWriteLockTest {
public static void main(String[] args) {
final Queue3 q3 = new Queue3();
for(int i=0;i<3;i++)
{
new Thread(){
public void run(){
while(true){
q3.get();
}
}

}.start();

new Thread(){
public void run(){
while(true){
q3.put(new Random().nextInt(10000));
}
}

}.start();
}

}
}

class Queue3{
private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。

public void get(){
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();
}
}

public void put(Object data){
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();
}
}
}

结果如下


Thread-0 be ready to read data!


Thread-1 be ready to write data!


Thread-2 be ready to read data!


Thread-3 be ready to write data!


Thread-4 be ready to read data!


Thread-5 be ready to write data!


Thread-0have read data :null


Thread-0 be ready to read data!


Thread-3 have write data: 5280


Thread-3 be ready to write data!


Thread-1 have write data: 5839


Thread-1 be ready to write data!


Thread-4have read data :5839


我们可以看到 读中有写  写中有写 写中有读 完全乱套了。



我们试试个两个方法加上synchronized 结果如下


Thread-0 be ready to read data!


Thread-0have read data :null


Thread-5 be ready to write data!


Thread-5 have write data: 7931


Thread-5 be ready to write data!


Thread-5 have write data: 9564


Thread-5 be ready to write data!


Thread-5 have write data: 1203


Thread-5 be ready to write data!


Thread-5 have write data: 8870


Thread-4 be ready to read data!


Thread-4have read data :8870


Thread-3 be ready to write data!


Thread-3 have write data: 9334


Thread-3 be ready to write data!


Thread-3 have write data: 2680


Thread-3 be ready to write data!


Thread-3 have write data: 9948


Thread-3 be ready to write data!


Thread-3 have write data: 375


Thread-2 be ready to read data!


读与写完全互斥,读的时候不写,写的时候不读。满足一二准则。



读写锁

为了实现准则三,在java5中的出现了读写锁。


java.util.concurrent.locks Interface ReadWriteLock


ReadWriteLock有两个方法


Lock     readLock()   Returns the lock used for reading.


Lock     writeLock()  Returns the lock used for writing.


得到两种锁后,就可以调用锁的lock与unlock方法了。


一般使用它的子类ReentrantReadWriteLock来产生ReadWriteLock


其签名如下:


public class ReentrantReadWriteLock extends Object implements ReadWriteLock, Serializable

看看使用方法


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(20);
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(20);
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}

结果如下


Thread-5 have write data: 7329


Thread-0              be ready to read data!


Thread-0       have read data :7329


Thread-1 be ready to write data!


Thread-1 have write data: 1361


Thread-2              be ready to read data!


Thread-4              be ready to read data!


Thread-0              be ready to read data!


Thread-2       have read data :1361


Thread-2              be ready to read data!


Thread-4       have read data :1361


我们可以看到 线程1的写是完全互斥的。


而线程2 4 0的读是可以同步进行的。

这是读写锁最简单的例子,下一节,我们看一个稍微复杂的,把读锁与写锁放到一个方法内的例子。

感谢glt

举报

相关推荐

0 条评论