ReadWriteLock读写锁

1、引入ReadWriteLock读写锁

ReadWriteLock是JDK5中提供的读写分离锁。读写分离锁可以有效地帮助减少锁竞争,以提升系统的性能。用锁分离的机制来提升性能非常容易理解,比如线程A1、A2、A3进行写操作,B1、B2、B3进行读操作,如果使用重入锁或者内部锁,则理论上说所有读之间,读与写之间、写与写之间都是串行操作。当B1进行读取时,B2、B3则需要等待锁。由于读操作并不会对数据的完整性造成破坏,这种等待显然是不合理的。因此,读写锁就有了发挥功能的余地。

在这种情况下,读写锁允许多个线程同时读,使得B1、B2、B3之间真正的并行。但是考虑到数据的完整性,写写操作和读写操作之间依然是需要相互等待和持有锁的。总的来说读写锁的访问约束如下:

  • 读-读不互斥:读读之间不阻塞
  • 读-写互斥:读阻塞写,写也会阻塞读。
  • 写写互斥:写写阻塞

如果在系统中,读操作次数远远大于写操作,则读写锁就可以发挥最大的功效,提升系统的性能。

2、简单演示一下ReadWriteLock对性能的帮助

演示代码如下:

public class ReadWriteLockDemo
{
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();
    private int value;

    public Object handleRead(Lock lock) throws InterruptedException
    {
        try
        {
            lock.lock();    //模拟读操作
            Thread.sleep(1000); //读操作的耗时越多,读写锁的优势就越明显
            return value;
        }
        finally
        {
            lock.unlock();
        }
    }

    public void handleWrite(Lock lock, int index) throws InterruptedException
    {
        try
        {
            lock.lock();    //模拟写操作
            Thread.sleep(1000);
            value = index;
        }
        finally
        {
            lock.unlock();
        }
    }


    public static void main(String[] args)
    {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();
        Runnable readRunnable = new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                     demo.handleRead(readLock);
//                     demo.handleRead(lock);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        };

        Runnable writeRunnable = new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    demo.handleWrite(writeLock, new Random().nextInt());
                    demo.handleWrite(lock, new Random().nextInt());
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        };
        
        for (int i = 0; i < 18; ++i)
        {
            new Thread(readRunnable).start();
        }

        for (int i = 18; i < 20; ++i)
        {
            new Thread(writeRunnable).start();
        }
    }
}

上述代码中,在读写操作时分别模拟了一个非常耗时的操作,让线程等到1秒钟。他们分别对应读写耗时。当进行读操作时,使用读锁,进行写操作时,使用写锁。然后开启18个读线程和两个写线程。由于这里使用了读写分离,因此读线程完全并行,而写线程会阻塞读,因此,实际上这段代码运行大约2秒就会结束(写线程之间实际是串行的)。如果用重入锁代替读写锁,那么所有的读写线程之间都必须相互等待,因此整个程序的执行时间将长达20余秒。

推荐阅读更多精彩内容