多线程之读写锁

前言

java中,锁lock是多线程编程的一个重要组件,可以说凡是涉及到多线程编程,线程安全这一块就无法避开lock,进一步说就是所有的线程安全都是基于锁实现的,只是从形式上分为隐式锁和显式锁,synchronized就属于隐式锁,像我们之前分享的可重入锁就属于显式锁,当然显示锁还有很多,我们今天就来看一个很常用的显式锁——读写锁。

读写锁

读写锁是一对互斥锁,分为读锁和写锁。读锁和写锁互斥,让一个线程在进行读操作时,不允许其他线程的写操作,但是不影响其他线程的读操作;当一个线程在进行写操作时,不允许任何线程进行读操作或者写操作。

简单来说就是,写锁会排斥读和写,但是读锁只排斥写,这样的好处就很明显,在读多写少的应用场景下,比其他互斥锁性能要好很多。下面我们通过一段代码说明:

public class Example {
    private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
        ExecutorService executorService = Executors.newCachedThreadPool();

        // 写操作1:写锁
        executorService.execute(() -> {
            writeLock.lock();
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("写count1: " + count.incrementAndGet());
            }
            writeLock.unlock();
        });
        // 读操作1:读锁
        executorService.execute(() -> {
            readLock.lock();
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("读count1: " + count.get());
            }
            readLock.unlock();
        });
        // 读操作2:读锁
        executorService.execute(() -> {
            readLock.lock();
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("读count2: " + count.get());
            }
            readLock.unlock();
        });
        // 写操作2:写锁
        executorService.execute(() -> {
            writeLock.lock();
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("写count2: " + count.incrementAndGet());
            }
            writeLock.unlock();
        });
        // 写操作3:写锁
        executorService.execute(() -> {
            writeLock.lock();
            for (int i = 0; i < 5; i++) {
                System.out.println("写count3: " + count.incrementAndGet());
            }
            writeLock.unlock();
        });

        executorService.shutdown();

    }
}

上面的代码中,我们分别有3次写操作,2次读操作,然后运行上面的代码:

运行结果图片上我们已经标注了线程休眠情况,根据运行结果虽然写count1休眠了1000,读count1休眠了500,但因为写锁的存在,排斥了其他读写操作,读只能等写锁释放,所以是先写后读,也就是写锁排斥读;

count2休眠了200,读count1休眠了500,虽然加了读锁,但结果还是读count2先运行,而且期间是读count1和读count2交替运行,说明读并不排斥读,而且读后面的写count2只休眠了100,但还是在读count1和读count2之后运行,说明读排斥写;

count2休眠100,写count3未休眠,但是写count3依然在写count2之后执行,说明写锁排斥写。

综上,我们在示例中分别证明了我们上面的结论:写锁会排斥读和写,但是读锁只排斥写。

总结

读写锁相比于其他互斥锁,优点很明显,也就是前面我们说的:在读多写少的应用场景下,比其他互斥锁性能要好很多。至于原因,我们也通过示例证明了:写锁会排斥读和写,但是读锁只排斥写。

关于读写锁,我觉得今天分享的内容已经比较详细,不仅展示了它的基本用法,同时还通过示例反证了它的互斥原则,所以如果你看明白了今天的内容,那么互斥锁这一款的知识你就掌握了。好了,今天的内容就到这里吧!

原文地址:https://www.cnblogs.com/caoleiCoding/p/15015069.html