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;
	}	
		
}

输出结果:

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

在上面的例子中,设置了一个账号。金额为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();
    }

    原文作者:java锁
    原文地址: https://blog.csdn.net/evankaka/article/details/51932044
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞