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

来源:互联网 发布:汉仪字体淘宝侵权 编辑:程序博客网 时间:2024/05/29 13:19

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

一、读写锁ReadWriteLock

       在上文中回顾了并发包中的可重入锁ReentrantLock,并且也分析了它的源码。从中我们知道它是一个单一锁(笔者自创概念),意思是在多人读、多人写、或同时有人读和写时。只能有一个人能拿到锁,执行代码。但是在很多场景。我们想控制它能多人同时读,但是又不让它多人写或同时读和写时。(想想这是不是和数据库的可重复读有点类型?),这时就可以使用读写锁:ReadWriteLock。

下面来看一个应用

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.lin;  
  2.   
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5. import java.util.concurrent.locks.ReadWriteLock;  
  6. import java.util.concurrent.locks.ReentrantReadWriteLock;  
  7.   
  8. public class ReadWriteLockTest {  
  9.       
  10.    public  static  void main(String[] args) {  
  11.        //创建一个锁对象 ,非公平锁  
  12.        ReadWriteLock lock = new ReentrantReadWriteLock(false);    
  13.        //创建一个线程池    
  14.        ExecutorService pool = Executors.newCachedThreadPool();   
  15.        //设置一个账号,设置初始金额为10000  
  16.        Account account = new Account(lock,"123456",10000);  
  17.           
  18.        //账号取钱10次,存钱10次,查询20次  
  19.        for(int i=1;i<=10;i++) {  
  20.            Operation operation1 = new Operation(account,"take");  
  21.            Operation operation2 = new Operation(account,"query");  
  22.            Operation operation3 = new Operation(account,"save");  
  23.            Operation operation4 = new Operation(account,"query");  
  24.            pool.execute(operation1);  
  25.            pool.execute(operation2);  
  26.            pool.execute(operation3);  
  27.            pool.execute(operation4);  
  28.        }  
  29.        pool.shutdown();  
  30.        while(!pool.isTerminated()){    
  31.            //wait for all tasks to finish    
  32.        }    
  33.        System.out.println("账号"+ account.getAccoutNo() +",最后金额为:"+account.getMoney());    
  34.    }  
  35.   
  36. }  
  37.   
  38. class Operation implements Runnable{  
  39.       
  40.     private Account account;//账号  
  41.           
  42.     private String type;  
  43.       
  44.     Operation(Account account,String type){  
  45.         this.account = account;  
  46.         this.type = type;  
  47.     }  
  48.       
  49.   
  50.     public void run() {  
  51.         if ("take".equals(type)) { //每次取100元  
  52.              //获取写锁    
  53.             account.getLock().writeLock().lock();  
  54.             account.setMoney(account.getMoney() -100);  
  55.             System.out.println( "取走100元,账号"+ account.getAccoutNo()+" 还有"+account.getMoney()+"元");  
  56.             account.getLock().writeLock().unlock();  
  57.               
  58.         }  
  59.         else if ("query".equals(type)) {  
  60.              //获取写锁    
  61.             account.getLock().readLock().lock();  
  62.             System.out.println( "查询账号"+ account.getAccoutNo()+" 还有"+account.getMoney()+"元");  
  63.             account.getLock().readLock().unlock();  
  64.               
  65.         }  
  66.         else if ("save".equals(type)) {  
  67.              //获取写锁    
  68.             account.getLock().writeLock().lock();  
  69.             account.setMoney(account.getMoney() + 100);  
  70.             System.out.println( "存入100元,账号"+ account.getAccoutNo()+" 还有"+account.getMoney()+"元");  
  71.             account.getLock().writeLock().unlock();  
  72.         }  
  73.     }  
  74.       
  75. }  
  76.   
  77. class Account  {  
  78.       
  79.     private int money;//账号上的钱  
  80.       
  81.     private ReadWriteLock lock;//读写写  
  82.       
  83.     private String accoutNo;//账号  
  84.       
  85.     Account(ReadWriteLock lock,String accoutNo,int money) {  
  86.         this.lock = lock;  
  87.         this.accoutNo = accoutNo;  
  88.         this.money = money;  
  89.     }  
  90.       
  91.     public int getMoney() {  
  92.         return money;  
  93.     }  
  94.   
  95.     public void setMoney(int money) {  
  96.         this.money = money;  
  97.     }  
  98.   
  99.     public ReadWriteLock getLock() {  
  100.         return lock;  
  101.     }  
  102.   
  103.     public void setLock(ReadWriteLock lock) {  
  104.         this.lock = lock;  
  105.     }  
  106.   
  107.     public String getAccoutNo() {  
  108.         return accoutNo;  
  109.     }  
  110.   
  111.     public void setAccoutNo(String accoutNo) {  
  112.         this.accoutNo = accoutNo;  
  113.     }     
  114.           
  115. }  
输出结果:


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

二、源码分析 
1、ReadWriteLock
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface ReadWriteLock {  
  2.   
  3.     Lock readLock();//返回读锁  
  4.   
  5.     Lock writeLock();//返回写锁  
  6. }  

ReadWriteLock就只是一个接口类,真正实现 类在ReentrantReadWriteLock
2、ReentrantReadWriteLock
(1)包含变量
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class ReentrantReadWriteLock  
  2.         implements ReadWriteLock, java.io.Serializable {  
  3.     private static final long serialVersionUID = -6992448646407690164L;  
  4.     /**读锁*/  
  5.     private final ReentrantReadWriteLock.ReadLock readerLock;  
  6.     /** 写锁 */  
  7.     private final ReentrantReadWriteLock.WriteLock writerLock;  
  8.     /** 内部类,在ReentrantLock也有它 */  
  9.     final Sync sync;  

看了一个它的变量还是比较简单的,其中Sync类在ReentrantLock类中笔者已介绍过。
(2)构造函数 
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public ReentrantReadWriteLock() {  
  2.     this(false);  
  3. }  
  4.   
  5. /** 
  6.  * 设置读写锁 
  7.  */  
  8. public ReentrantReadWriteLock(boolean fair) {  
  9.     sync = fair ? new FairSync() : new NonfairSync();//是否公平  
  10.     readerLock = new ReadLock(this);  
  11.     writerLock = new WriteLock(this);  
  12. }  

看了看FairSync的源码其实和上节中讲的基本一样。这里就不再展开。
(3)ReadLock
看了下读锁,其实很简单,发现里面封装的都 是调用Sync类的方法,看来它才是重点。在ReadLock类的lock方法中,我们看到了sync.acquireShared(1);这里就可以认为是一个共享锁
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static class ReadLock implements Lock, java.io.Serializable {  
  2.     private static final long serialVersionUID = -5992448646407690164L;  
  3.     private final Sync sync; //和上面的一样,是同一个类  
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. protected ReadLock(ReentrantReadWriteLock lock) {  
  2.     sync = lock.sync;  
  3. }  
  4.   
  5. public void lock() {  
  6.     sync.acquireShared(1); //共享锁  
  7. }  
  8.   
  9.   
  10. public void lockInterruptibly() throws InterruptedException {  
  11.     sync.acquireSharedInterruptibly(1);//响应中断,跳出阻塞  
  12. }  
  13.   
  14.   
  15. public boolean tryLock() {  
  16.     return sync.tryReadLock();//取得锁才返回true  
  17. }  
  18.   
  19. public boolean tryLock(long timeout, TimeUnit unit)  
  20.         throws InterruptedException {  
  21.     return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));  
  22. }  
  23. public void unlock() {  
  24.     sync.releaseShared(1);//释放锁  
  25. }  
再进来看看
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final void acquireShared(int arg) {  
  2.     if (tryAcquireShared(arg) < 0)  
  3.         doAcquireShared(arg);  
  4. }  
再进去就看不了,这里的方法其实就是认为取得一个共享锁。

(4)WriteLock
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static class WriteLock implements Lock, java.io.Serializable {  
  2.        private static final long serialVersionUID = -4992448646407690164L;  
  3.        private final Sync sync;  
  4.   
  5.        protected WriteLock(ReentrantReadWriteLock lock) {  
  6.            sync = lock.sync;  
  7.        }  
  8.   
  9.        public void lock() {  
  10.            sync.acquire(1); //表明只是取得一个锁,但不是独占  
  11.        }  
  12.   
  13.        public void lockInterruptibly() throws InterruptedException {  
  14.            sync.acquireInterruptibly(1);  
  15.        }  
在WriteLock锁中的lock方法和ReadLock是有所不同的。WriteLock它是一个独占锁。也就是有线程拿到后,其它线程就得阻塞等待了。
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final void acquire(int arg) {  
  2.     if (!tryAcquire(arg) &&  
  3.         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  
  4.         selfInterrupt();  
  5. }  
0 0
原创粉丝点击