您的位置:首页 > 编程语言 > Java开发

Java并发编程与技术内幕:聊聊锁的技术内幕(中)

2016-07-18 08:46 513 查看
      摘要:本文主要讲了读写锁。
一、读写锁ReadWriteLock
       在上文中回顾了并发包中的可重入锁ReentrantLock,并且也分析了它的源码。从中我们知道它是一个单一锁(笔者自创概念),意思是在多人读、多人写、或同时有人读和写时。只能有一个人能拿到锁,执行代码。但是在很多场景。我们想控制它能多人同时读,但是又不让它多人写或同时读和写时。(想想这是不是和数据库的可重复读有点类型?),这时就可以使用读写锁:ReadWriteLock。
下面来看一个应用
package com.lin;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {

public static void main(String[] args) {
//创建一个锁对象 ,非公平锁
ReadWriteLock lock = new ReentrantReadWriteLock(false);
//创建一个线程池
ExecutorService pool = Executors.newCachedThreadPool();
//设置一个账号,设置初始金额为10000
Account account = new Account(lock,"123456",10000);

//账号取钱10次,存钱10次,查询20次
for(int i=1;i<=10;i++) {
Operation operation1 = new Operation(account,"take");
Operation operation2 = new Operation(account,"query");
Operation operation3 = new Operation(account,"save");
Operation operation4 = new Operation(account,"query");
pool.execute(operation1);
pool.execute(operation2);
pool.execute(operation3);
pool.execute(operation4);
}
pool.shutdown();
while(!pool.isTerminated()){
//wait for all tasks to finish
}
System.out.println("账号"+ account.getAccoutNo() +",最后金额为:"+account.getMoney());
}

}

class Operation implements Runnable{

private Account account;//账号

private String type;

Operation(Account account,String type){
this.account = account;
this.type = type;
}

public void run() {
if ("take".equals(type)) { //每次取100元
//获取写锁
account.getLock().writeLock().lock();
account.setMoney(account.getMoney() -100);
System.out.println( "取走100元,账号"+ account.getAccoutNo()+" 还有"+account.getMoney()+"元");
account.getLock().writeLock().unlock();

}
else if ("query".equals(type)) {
//获取写锁
account.getLock().readLock().lock();
System.out.println( "查询账号"+ account.getAccoutNo()+" 还有"+account.getMoney()+"元");
account.getLock().readLock().unlock();

}
else if ("save".equals(type)) {
//获取写锁
account.getLock().writeLock().lock();
account.setMoney(account.getMoney() + 100);
System.out.println( "存入100元,账号"+ account.getAccoutNo()+" 还有"+account.getMoney()+"元");
account.getLock().writeLock().unlock();
}
}

}

class Account {

private int money;//账号上的钱

private ReadWriteLock lock;//读写写

private String accoutNo;//账号

Account(ReadWriteLock lock,String accoutNo,int money) {
this.lock = lock;
this.accoutNo = accoutNo;
this.money = money;
}

public int getMoney() {
return money;
}

public void setMoney(int money) {
this.money = money;
}

public ReadWriteLock getLock() {
return lock;
}

public void setLock(ReadWriteLock lock) {
this.lock = lock;
}

public String getAccoutNo() {
return accoutNo;
}

public void setAccoutNo(String accoutNo) {
this.accoutNo = accoutNo;
}

}
输出结果:


在上面的例子中,设置了一个账号。金额为10000,然后开了10条线程每次取100,10条线程每次存100,20条线程一直查。从结果中我们可以看到是正确的。
二、源码分析 1、ReadWriteLock
public interface ReadWriteLock {

Lock readLock();//返回读锁

Lock writeLock();//返回写锁
}

ReadWriteLock就只是一个接口类,真正实现 类在ReentrantReadWriteLock
2、ReentrantReadWriteLock(1)包含变量
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/**读锁*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 写锁 */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** 内部类,在ReentrantLock也有它 */
final Sync sync;

看了一个它的变量还是比较简单的,其中Sync类在ReentrantLock类中笔者已介绍过。(2)构造函数 
public ReentrantReadWriteLock() {
this(false);
}

/**
* 设置读写锁
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//是否公平
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}

看了看FairSync的源码其实和上节中讲的基本一样。这里就不再展开。(3)ReadLock看了下读锁,其实很简单,发现里面封装的都 是调用Sync类的方法,看来它才是重点。在ReadLock类的lock方法中,我们看到了sync.acquireShared(1);这里就可以认为是一个共享锁
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();//取得锁才返回true
}

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);//释放锁
}
再进来看看
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
再进去就看不了,这里的方法其实就是认为取得一个共享锁。
(4)WriteLock
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);
}
在WriteLock锁中的lock方法和ReadLock是有所不同的。WriteLock它是一个独占锁。也就是有线程拿到后,其它线程就得阻塞等待了。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: