首页 > 其他 > 详细

Java 7之多线程第11篇 - 读写锁

时间:2014-02-15 13:08:02      阅读:357      评论:0      收藏:0      [点我收藏+]

ReadWriteLock是ReentrantReadWriteLock的接口,而ReentrantReadWriteLock实现类中包括子类ReadLock和WriteLock。

首先来看一下ReadWriteLock接口中方法的定义:

public interface ReadWriteLock {
	Lock readLock();  // 返回用于读取操作的锁
	Lock writeLock(); // 返回用于写入操作的锁
}
读取锁和写入锁不可以同时存储,且读取锁可以同时存在多个,但是写入锁只能存在一个。
来看实现类中部分变量和方法,如下:

    private final ReentrantReadWriteLock.ReadLock readerLock; // 读锁
    private final ReentrantReadWriteLock.WriteLock writerLock;// 写锁
    
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    // 默认为非公平锁
    public ReentrantReadWriteLock() {
        this(false);
    }
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

定义了读锁和写锁变量,同时提供了两个构造函数,用来构造公平或非公平锁。


1、获取共享读锁


ReadLock内部类的实现源代码如下:

    // 获取读锁,是共享锁
    public static class ReadLock implements Lock, java.io.Serializable {
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        public void lock() { // 共享读锁的获取
            sync.acquireShared(1);
        }

        //Acquires the read lock unless the current thread is interrupted.
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(1);
        }

        public  boolean tryLock() {
            return sync.tryReadLock();
        }

        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
        }

        public  void unlock() {
            sync.releaseShared(1);
        }
        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }
        public String toString() {
            int r = sync.getReadLockCount();
            return super.toString() +
                "[Read locks = " + r + "]";
        }
    }

读取锁是通过调用lock()方法来获取的,在这个方法中调用了acquireShared()方法,这个方法在AQS中实现,如下:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
前面已经多次碰到这相同代码,不解释,直接来看tryAcquireShared()方法的实现:

 protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();        // 获取锁的状态
            // 如果锁是互斥锁,并且获取锁的线程不是当前线程,则返回-1,表示获取失败
            if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);     // 获取读取锁的共享计数
            // 如果不需要阻塞等待,并且读取锁的共享计数小于MAX_COUNT;
            // 则通过CAS函数更新锁的状态,将读取锁的共享计数+1。
            if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {           // 第1次获取读取锁
                    firstReader = current;
                    firstReaderHoldCount = 1;
                // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
                } else if (firstReader == current) { 
                    firstReaderHoldCount++;
                } else {
                    // HoldCounter是用来统计该线程获取读取锁的次数。
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != current.getId())
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;         // 将该线程获取读取锁的次数+1
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }
tryAcquireShared()的作用是尝试获取共享锁,如果通过如上的方法获取失败,则调用fullTryAcquireShared()方法来获取:

final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();                           // 获取锁的状态
                if (exclusiveCount(c) != 0) {                 // 写线程获取互斥锁 
                    if (getExclusiveOwnerThread() != current) // 获取锁的线程不是当前线程
                        return -1;
                } else if (readerShouldBlock()) { // 需要阻塞等待
                    if (firstReader == current) { // 当前线程是第一个线程
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != current.getId()) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)// 如果当前线程获取锁的计数为0,则返回-1。
                            return -1;
                    }
                }
                // 不需要阻塞等待,获取读取锁的共享统计数;如果共享统计数超过MAX_COUNT,则抛出异常
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 将线程获取读取锁的次数加1。
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    // 如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount。
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,
                    // 则将firstReaderHoldCount+1。
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != current.getId())
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        // 更新线程的获取“读取锁”的共享计数
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }
doAcquireShared()定义在AQS函数中,源码如下:

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
这段代码与前面的实现相同,在此不估过多的解释。

2、释放共享读锁


调用ReadLock中的unlock()方法来释放共享读锁,在这个方法中调用了如下的方法:

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
 protected final boolean tryReleaseShared(int unused) {
            // 获取当前线程,即释放共享锁的线程
            Thread current = Thread.currentThread();
            // 如果想要释放锁的线程(current)是第1个获取锁(firstReader)的线程,
            // 并且第1个获取锁的线程获取锁的次数=1,则设置firstReader为null;
            // 否则,将第1个获取锁的线程的获取次数-1。
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
            	// 获取rh对象,并更新当前线程获取锁的信息
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != current.getId())
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();         // 获取锁的状态
                int nextc = c - SHARED_UNIT;// 将锁的获取次数-1
                // 通过CAS更新锁的状态
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }


private void doReleaseShared() {
    for (;;) {
        // 获取CLH队列的头节点
        Node h = head;
        // 如果头节点不为null,并且头节点不等于tail节点。
        if (h != null && h != tail) {
            // 获取头节点对应的线程的状态
            int ws = h.waitStatus;
            // 如果头节点对应的线程是SIGNAL状态,则意味着“头节点的下一个节点所对应的线程”需要被unpark唤醒。
            if (ws == Node.SIGNAL) {
                // 设置“头节点对应的线程状态”为空状态。失败的话,则继续循环。
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                // 唤醒“头节点的下一个节点所对应的线程”。
                unparkSuccessor(h);
            }
            // 如果头节点对应的线程是空状态,则设置“文件点对应的线程所拥有的共享锁”为其它线程获取锁的空状态。
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 如果头节点发生变化,则继续循环。否则,退出循环。
        if (h == head)                   // loop if head changed
            break;
    }
}


3、读取锁的公正锁和非公平锁

公平锁和非公平锁的区别,体现在判断是否需要阻塞的函数readerShouldBlock()是不同的。公平锁的readerShouldBlock()的源码如下:

final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}
在公平共享锁中,如果在当前线程的前面有其他线程在等待获取共享锁,则返回true;否则,返回false。
非公平锁的readerShouldBlock()的源码如下:
final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}
在非公平共享锁中,它会无视当前线程的前面是否有其他线程在等待获取共享锁。只要该非公平共享锁对应的线程不为null,则返回true


4、举例


public class ReadWriteLockTest1 { 

    public static void main(String[] args) { 
        // 创建账户
        MyCount myCount = new MyCount("4238920615242830", 10000); 
        // 创建用户,并指定账户
        User user = new User("Tommy", myCount); 
        // 分别启动3个“读取账户金钱”的线程 和 3个“设置账户金钱”的线程
        for (int i=0; i<3; i++) {
            user.getCash();
            user.setCash((i+1)*1000);
        }
    } 
} 

class User {
    private String name;            //用户名 
    private MyCount myCount;        //所要操作的账户 
    private ReadWriteLock myLock;   //执行操作所需的锁对象 

    User(String name, MyCount myCount) {
        this.name = name; 
        this.myCount = myCount; 
        this.myLock = new ReentrantReadWriteLock();
    }

    public void getCash() {
        new Thread() {
            public void run() {
                myLock.readLock().lock(); 
                try {
                    System.out.println(Thread.currentThread().getName() +" getCash start"); 
                    myCount.getCash();
                    Thread.sleep(1);
                    System.out.println(Thread.currentThread().getName() +" getCash end"); 
                } catch (InterruptedException e) {
                } finally {
                    myLock.readLock().unlock(); 
                }
            }
        }.start();
    }

    public void setCash(final int cash) {
        new Thread() {
            public void run() {
                myLock.writeLock().lock(); 
                try {
                    System.out.println(Thread.currentThread().getName() +" setCash start"); 
                    myCount.setCash(cash);
                    Thread.sleep(1);
                    System.out.println(Thread.currentThread().getName() +" setCash end"); 
                } catch (InterruptedException e) {
                } finally {
                    myLock.writeLock().unlock(); 
                }
            }
        }.start();
    }
}

class MyCount {
    private String id;         //账号 
    private int    cash;       //账户余额 

    MyCount(String id, int cash) { 
        this.id = id; 
        this.cash = cash; 
    } 

    public String getId() { 
        return id; 
    } 

    public void setId(String id) { 
        this.id = id; 
    } 

    public int getCash() { 
        System.out.println(Thread.currentThread().getName() +" getCash cash="+ cash); 
        return cash; 
    } 

    public void setCash(int cash) { 
        System.out.println(Thread.currentThread().getName() +" setCash cash="+ cash); 
        this.cash = cash; 
    } 
}
运行的结果如下:

Thread-0 getCash start
Thread-2 getCash start
Thread-0 getCash cash=10000
Thread-2 getCash cash=10000
Thread-0 getCash end
Thread-2 getCash end
Thread-1 setCash start
Thread-1 setCash cash=1000
Thread-1 setCash end
Thread-3 setCash start
Thread-3 setCash cash=2000
Thread-3 setCash end
Thread-4 getCash start
Thread-4 getCash cash=2000
Thread-4 getCash end
Thread-5 setCash start
Thread-5 setCash cash=3000
Thread-5 setCash end



























Java 7之多线程第11篇 - 读写锁

原文:http://blog.csdn.net/mazhimazh/article/details/19187839

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!