读写锁


一、引言

锁(Lock)是java一个很重要的同步组件,Lock提供了跟 synchronized 关键字一样的功能,相比 synchronized 更加灵活,但是实现也更加复杂。

锁的分类:
锁主要分为排他锁和读写锁。

  • 排他锁:在同一时刻只允许一个线程进行访问,其他线程等待;
  • 读写锁:在同一时刻允许多个读线程访问,但是当写线程访问,所有的写线程和读线程均被阻塞。读写锁维护了一个读锁加一个写锁,通过读写锁分离的模式来保证线程安全,性能高于一般的排他锁。

java并发包提供了读写锁 ReadWriteLock 的具体实现 ReentranReadWriteLock,它主要提供了一下特性:

  1. 公平性选择:支持公平和非公平(默认)两种获取锁的方式,非公平锁的吞吐量优于公平锁;
  2. 可重入:支持可重入,读线程在获取读锁之后能够再次获取读锁,写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁;
  3. 锁降级:线程获取锁的顺序遵循获取写锁,获取读锁,释放写锁,写锁可以降级成为读锁。

二、读写锁的分析和实现

(1)示例:

情景:一个家庭中有张三、爸爸、妈妈、大姐、二姐和弟弟,有一个家庭账号,每个家庭成员都可以存钱取钱(写操作)和查询(读操作)。我们使用程序模拟以上情景:

账户类:

package basis.StuReadWriteSync;

public class HomeCount {
    private String oid;
    private int cash;

    public HomeCount(String oid, int cash) {
        this.oid = oid;
        this.cash = cash;
    }
    public HomeCount() { }

    public String getOid() {
        return oid;
    }

    public void setOid(String oid) {
        this.oid = oid;
    }

    public int getCash() {
        return cash;
    }

    public void setCash(int cash) {
        this.cash = cash;
    }

    @Override
    public String toString() {
        return "HomeCount{" +
                "oid='" + oid + '\'' +
                ", cash=" + cash +
                '}';
    }
}

家庭成员类:

package basis.StuReadWriteSync;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;

public class FamilyMember implements Runnable{
    private String name;
    private HomeCount homeCount;
    private int iocash;
    private ReadWriteLock mylock;
    private boolean ischeck;

    public FamilyMember(String name, HomeCount homeCount, int iocash, ReadWriteLock mylock, boolean ischeck) {
        this.name = name;
        this.homeCount = homeCount;
        this.iocash = iocash;
        this.mylock = mylock;
        this.ischeck = ischeck;
    }

    @Override
    public void run() {
        if(ischeck){
            mylock.readLock().lock();
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("读:"+name+"正在查询"+homeCount.getOid()+"账户,余额:"+homeCount.getCash());
            }catch (Exception e){
                e.printStackTrace();
            }
            finally {
                mylock.readLock().unlock();
            }

        }else{
            mylock.writeLock().lock();
            try {
                //线程休眠两秒
                TimeUnit.SECONDS.sleep(2);

                System.out.println("写:"+name+"正在操作"+homeCount.getOid()+"账户,金额"+ iocash+",余额:"+homeCount.getCash());
                homeCount.setCash(homeCount.getCash()+iocash);
                System.out.println("写:"+name+"操作"+homeCount.getOid()+"账户成功,金额"+ iocash+",余额:"+homeCount.getCash());
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                mylock.writeLock().unlock();
            }
        }
    }
}

测试类:

package basis.StuReadWriteSync;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestHomeCount {
    public static void main(String[] args) {
        HomeCount homeCount = new HomeCount("001",10000);
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);
        ExecutorService pool = Executors.newFixedThreadPool(2);

        FamilyMember me = new FamilyMember("张三",homeCount,-2000,lock,false);
        FamilyMember brother = new FamilyMember("弟弟",homeCount,-3000,lock,false);
        FamilyMember father = new FamilyMember("爸爸",homeCount,0,lock,true);
        FamilyMember sister_2 = new FamilyMember("二姐",homeCount,0,lock,true);
        FamilyMember sister_1 = new FamilyMember("大姐",homeCount,0,lock,true);
        FamilyMember mom = new FamilyMember("妈妈",homeCount,3000,lock,false);

        pool.execute(me);
        pool.execute(brother);
        pool.execute(father);
        pool.execute(sister_2);
        pool.execute(sister_1);
        pool.execute(mom);
        pool.shutdown();
    }
}

测试结果:

写:张三正在操作001账户,金额-2000,余额:10000
写:张三操作001账户成功,金额-2000,余额:8000
写:弟弟正在操作001账户,金额-3000,余额:8000
写:弟弟操作001账户成功,金额-3000,余额:5000
读:二姐正在查询001账户,余额:5000
读:爸爸正在查询001账户,余额:5000
读:大姐正在查询001账户,余额:5000
写:妈妈正在操作001账户,金额3000,余额:5000
写:妈妈操作001账户成功,金额3000,余额:8000

(2)源码分析

ReadWriteLock:

package java.util.concurrent.locks;

/**
 * @see ReentrantReadWriteLock
 * @see Lock
 * @see ReentrantLock
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

ReadWriteLock 接口 在java.util.concurrent.locks包中,该接口只有两个方法,返回值均是Lock 类型的,一个获取读锁,一个获取写锁。

ReentrantReadWriteLock:

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    final Sync sync;
}

ReentrantReadWriteLock 类实现了 ReadWriteLock 接口和Serializable接口。

类中有一个ReadLock 类型的读锁对象( readLock),和一个WriterLock类型的写锁对象(WriteLock)。ReadLock和WriteLock 是 ReentrantReadWriteLock 的内部类,用于提供读锁和写锁的功能。这两个内部类都是 Lock 接口的实现类,同时还实现了Serializable 接口:

ReentrantReadWriteLock.ReadLock:

public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
}

ReentrantReadWriteLock.WriteLock:

public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
}

ReentrantReadWriteLock类中还有一个 Sync 类型的对象(sync),Sync 是 ReentrantReadWriteLock 中的一个抽象内部类,继承了 AbstractQueuedSynchronizer  接口。

abstract static class Sync extends AbstractQueuedSynchronizer {}

而在锁框架中,AbstractQueuedSynchronizer抽象类可以毫不夸张的说,占据着核心地位,它提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。

ReentrantReadWriteLock的构造方法:

public ReentrantReadWriteLock() {
        this(false);
    }


    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

ReentrantReadWriteLock中有两个构造方法,一个无参的构造器,默认使用非公平锁(NonfairSync),一个接受一个boolean类型的参数,为true时使用公平锁(FairSync),为 false 时使用非公平锁。

NonfairSync和FairSync都是 ReentrantReadWriteLock 类中的内部类,都继承了抽象类 Sync。

/**
     * Nonfair version of Sync
     */   
     static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {

            return apparentlyFirstQueuedIsExclusive();
        }
    }

    /**
     * Fair version of Sync
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

fairSync和NonfairSync主要区别为:

  • 如果当前线程不是锁的占有者,则NonfairSync并不判断是否有等待队列,直接使用compareAndSwap去进行锁的占用;
  • 如果当前线程不是锁的占有者,则FairSync则会判断当前是否有等待队列,如果有则将自己加到等待队列尾;