8

ReentrantReadWriteLock读写锁简单原理案例证明

 3 years ago
source link: http://www.cnblogs.com/jiaolian/p/14353892.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ReentrantReadWriteLock存在原因?

我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了List的多线程非安全问题,但是有个缺点: 读写同步,效率低下 。于是就出现了CopyOnWriteArrayList,它通过写时复制数组实现了读写分离, 提高了多线程对List读的效率,适合多读少些的情况 。同理:我们知道ReentrantLock,它是一把独占的锁,是用来控制线程同步的,如果我们用ReentrantLock来实现ArrayList安全,能否达到CopyOnWriteArrayList同样的效果呢?

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-26 15:49
 * @description:ReentrantReadWriteLock多读少写的场景
 * @modified By:
 * 公众号:叫练
 */
public class MultReadTest {


    private static class MyList {
        private final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
        private final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
        private final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();
        private List<String> list = new ArrayList();

        //读list
        public void readList() {
            try {
                READ_LOCK.lock();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+":"+list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                READ_LOCK.unlock();
            }
        }

        //写list
        public void writeList() {
            try {
                WRITE_LOCK.lock();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+":新增1个元素");
                list.add("叫练【公众号】");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                WRITE_LOCK.unlock();
            }
        }

    }

    public static void main(String[] args) {
        MyList myList = new MyList();
        //读写锁适合多读少写情况
        //新建10个读线程,1个写线程
        new Thread(()->{myList.writeList();},"写线程").start();
        for (int i=0; i<10; i++) {
            new Thread(()->{myList.readList();},"读线程"+(i+1)).start();
        }
    }

}

上面案例我们用ReentrantReadWriteLock实现了CopyOnWriteArrayList,主线程新建了1个写线程写list,10个读线程读list,程序一共花费2执行完毕,如果用Vector需要花费11秒。在多线程的情况下,通过读写锁操作List,提高了List的读效率,在List读的部分,线程是共享的,在对List写的过程中,在对写的线程是同步的,因此我们可以得出一个结论: 读写锁是读读共享,读写同步

独占获取锁简单流程

YVJJ73I.png!mobile 如上图,我们简单的梳理下独占获取锁流程。

  1. 独占锁获取(上述例子中的 WRITE_LOCK 写锁),首先判断是否有线程获取了锁, 是否有线程获取了锁的判断 通过读写锁中通过32位int类型state可以获取,其中低16位表示读锁,高16表示写锁。
  2. 有读锁:直接排队阻塞。
  3. 有写锁:还需要判断写锁线程是否是自己,如果是自己就是 锁重入 了,如果不是自己说明已经有其他的线程获取锁正在执行,那么当前线程需要排队阻塞。
  4. 无锁:直接获取锁,其他抢占的独占锁线程需要排队阻塞,当前线程执行完毕后释放锁通知下一个排队线程获取锁。

共享获取锁简单流程

fQ7FbuF.png!mobile 如上图,我们简单的梳理下共享锁获取锁流程。

  1. 独占锁获取(上述例子中的 READ_LOCK 读锁),首先判断是否有线程获取了锁。
  2. 有读锁:当前线程发现此时 读锁状态被占用,说明有线程获取了读锁 。该线程通过cas自旋【死循环】获取到读锁为止。
  3. 有写锁:还需要判断持有写锁的线程是否是自己,如果是自己而且此时是获取的是读锁会获取锁成功,我们称为 锁降级 ,如果不是自己说明此时有其他线程获取了写锁,那么当前线程需要排队阻塞。
  4. 无锁:直接获取锁。

写锁降级

我们说 读写互斥 ,但同一个线程中,先写后读也是允许的,我们称之为锁降级。在面试中共享锁面试频率也比较高,方便理解我们举个简单的案例说明下。

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-28 15:44
 * @description:ReentrantReadWriteLock读写锁降级测试
 * @modified By:
 * 公众号:叫练
 */
public class WriteLockLowerTest {

    private static final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
    private static final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
    private static final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();

    public static void main(String[] args) {
        try {
            WRITE_LOCK.lock();
            System.out.println("获取写锁");
            READ_LOCK.lock();
            System.out.println("获取读锁");
        } finally {
            READ_LOCK.unlock();
            System.out.println("释放写锁");
            WRITE_LOCK.unlock();
            System.out.println("释放读锁");
        }
    }
}

如上述代码:程序可以运行完毕,说明锁可以降级。 另外说一句,上面的程序先获取读锁再获取写锁,程序是会阻塞的,为什么呢? 欢迎小伙伴在留言区写下评论!

总结

今天我们用通俗易懂的文字描述了 ReentrantReadWriteLock 读写锁。喜欢的请点赞加评论哦! 点关注,不迷路,我是叫练【公众号】,边叫边练。期待我们下次再见!

ni6Rbmi.gif!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK