Java并发编程(二):ReentrantReadWriteLock的介绍与使用

Java并发编程(二):ReentrantReadWriteLock的介绍与使用

  • 概述
  • 1、公平锁与非公平锁(待补充)
  • 2、ReentrantReadWriteLock的可重入锁
  • 3、读写锁降级
  • 4、getReadHoldCount()与getReadLockCount()
  • 5、

概述

上面文中介绍的ReentrantLock是排他锁,同一时刻只允许一个线程访问。我们使用锁的目的是控制共享变量并发操作造成的线程安全问题。但有时共享变量提供读服务的是远超过写服务,为了提高并发时效率引入了读写锁。
在引入读写锁之前,所有获取锁的操作都需要上一个线程释放锁。
读写锁实现了读操作时获取读锁,写操作时获取写锁。
下表展示了读写锁的互斥关系。读锁实现共享,多个线程可以同时获得读写,使得线程不再阻塞在锁的获取上,提升并发效率。
1、A线程持有读锁时,B线程可以获取读锁,无法获取写锁。
2、A线程持有写锁时,B线程无法获取读锁,也无法获取写锁。

类型 读锁 写锁
读锁 不互斥 互斥
写锁 互斥 互斥

1、公平锁与非公平锁(待补充)

读写锁同样也分公平锁和非公平锁,并且默认的是非公平锁;这里我们就会产生疑问了,若不断有获取读锁的线程(后统称读线程,同理写线程)来获取读锁,读锁就可能永远都不会完全释放,会不会也会造成写线程饿死呢?下面分别介绍读写锁中的公平锁和非公平锁。

公平锁
读线程获取锁时,必须要同时满足下面情况才能得到锁,否则在CLH队列中等待:
1、当前线程是在CLH队列中等待最长的节点。
2、 上一线程持有的是读锁或者锁没有被任何线程获取。
写线程获取是,必须要满足下面情况才能得到锁,否则进入CLH队列中:
1、当前线程是在CLH队列中等待最长的节点。
2、锁没有被任何线程获取。
就是上面红色一小段逻辑不同,就能极大的提高并发读操作的性能。

非公平锁
读线程获取锁时:
读线程在获取锁,若等待时间最长的线程是写线程,必须让渡给该写线程优先获取锁。避免出现写线程"饿死"。
写线程获取锁时:
最快的写线程获得锁,其他线程继续等待。

***读写锁的公平锁和非公平锁相比复杂一些,后面我们会再针对源码剖析,把它看得更清楚和透彻。

CLH这个概念会多次提到。后面源码分析时,会详细剖析CLH的结构。这里我们可以暂时理解为链表

下面这段代码总觉得不够表达公平锁和非公平的特性。后续更深入了解后再补充!

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

public class ReadWriteLockClient {
    // public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);//非公平锁
        public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);//公平锁

    public static void main(String[] args) {
        Integer threadCount = 10;
        for (int i = 0; i < threadCount; i++) {
            if (i % 3 == 0) {
                Thread t = new Thread(new WriteWorker());
                t.start();
            } else {
                Thread t = new Thread(new ReadWorker());
                t.start();
            }

        }
    }

    static class ReadWorker implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-Read线程启动");
            lock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "-Read线程acquired,当前已获取读线程总数:" + lock.getReadLockCount() + " 已重入总次数:" + lock.getReadHoldCount());
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
// System.out.println(Thread.currentThread().getName() + "-读线程released");
        }
    }

    static class WriteWorker implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-Write线程启动");
            lock.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "-Write线程acquired,已重入总次数:" + lock.getWriteHoldCount());
            } finally {
                lock.writeLock().unlock();
            }
            System.out.println(Thread.currentThread().getName() + "-Write线程released");
        }
    }
}

2、ReentrantReadWriteLock的可重入锁

支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;
读锁的重入性:

public class ReadWriteLockClient {
    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public static Integer count = 5;

    public static void main(String[] args) {
        lockService();
    }

    public static void lockService() {
        lock.readLock().lock();
        while (--count > 0) {
            System.out.println(Thread.currentThread().getName() + " ReadHoldCount:" + lock.getReadHoldCount() + " ReadLockCount:" + lock.getReadLockCount());
            lockService();
        }
        lock.readLock().unlock();
    }
}

写锁的重入性:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockClient {
    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public static Integer count = 5;

    public static void main(String[] args) {
        lockService();
    }

    public static void lockService() {
        lock.writeLock().lock();
        while (--count > 0) {
            System.out.println(Thread.currentThread().getName() + " WriteHoldCount:" + lock.getWriteHoldCount() + " ReadLockCount:" + lock.getReadLockCount());
            lockService();
        }
        lock.writeLock().unlock();
    }
}

ReentrantReadWriteLock的重入特性和ReentrantLock的重入特性相似,不再赘述。

3、读写锁降级

锁降级指的是写锁降级成为读锁;
遵循获取写锁、获取读锁在释放写锁的顺序,写锁能够降级成为读锁。
但是,从读取锁升级到写入锁是不允许的;目的是为了保证数据的可见性。如果多个读锁获取了线程,其中任意线程成功获取写锁更新数据,对其他读线程来说是不可见的。
下面代码是对锁降级的一个简单示例

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockClient {
    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);//非公平锁

    public static void main(String[] args) {
        Thread t = new Thread(new Worker());
        t.start();
    }

    static class Worker implements Runnable {
        @Override
        public void run() {
            lock.writeLock().lock();
            System.out.println("获取写锁...");
            lock.readLock().lock();
            System.out.println("获取读锁...");
            lock.writeLock().unlock();
            System.out.println("释放写锁,未释放读锁,完成了锁降级...");
            System.out.println("此时是共享锁,其他线程可以获得读锁...");
            lock.readLock().unlock();
            System.out.println("释放读锁...");
        }
    }
}

Java并发编程(二):ReentrantReadWriteLock的介绍与使用_第1张图片

4、getReadHoldCount()与getReadLockCount()

getReadHoldCount():当前线程获取读锁的次数。
getReadLockCount():当前读锁被获取的次数(所有线程获取读锁的总数)。

附测试代码

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

public class ReadWriteLockClient {
    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(new Worker());
        t1.start();
        Thread t2 = new Thread(new Worker());
        t2.start();
    }

    static class Worker implements Runnable {

        @Override
        public void run() {
            lockService();
        }

        public void lockService() {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " ReadHoldCount:" + lock.getReadHoldCount() + " ReadLockCount:" + lock.getReadLockCount());
            lockService2();
            lock.readLock().unlock();

        }

        public void lockService2() {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " ReadHoldCount:" + lock.getReadHoldCount() + " ReadLockCount:" + lock.getReadLockCount());
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.readLock().unlock();

        }
    }
}

Java并发编程(二):ReentrantReadWriteLock的介绍与使用_第2张图片

5、

你可能感兴趣的:(Java并发编程)