Java并发编程与技术内幕:聊聊锁的技术内幕(中)
摘要:本文主要讲了读写锁。
一、读写锁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);
- }
再进去就看不了,这里的方法其实就是认为取得一个共享锁。
- 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 final void acquire(int arg) {
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }