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

来源:互联网 发布:布料模拟软件 编辑:程序博客网 时间:2024/06/07 06:24

      摘要:本文主要讲了读写锁。

一、读写锁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();    }



0 0