Java多线程:读写锁和两种同步方式的对比

这篇具有很好参考价值的文章主要介绍了Java多线程:读写锁和两种同步方式的对比。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

读写锁ReentrantReadWriteLock概述

大型网站中很重要的一块内容就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),但是效率非常低。所以在JDK中提供了一种读写锁ReentrantReadWriteLock,使用它可以加快运行效率。

读写锁表示两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。我把这两个操作理解为三句话:

1、读和读之间不互斥,因为读操作不会有线程安全问题

2、写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题

3、读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题

总结起来就是,多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

读和读共享

先证明一下第一句话"读和读之间不互斥",举一个简单的例子:

public class ThreadDomain48 extends ReentrantReadWriteLock {

        public void read() {

            try {

                readLock().lock();

                System.out.println(Thread.currentThread().getName() + "获得了读锁, 时间为" +

                    System.currentTimeMillis());

                Thread.sleep(10000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            } finally {

                readLock().unlock();

            }

        }

    }

    public static void main(String[] args) {

        final ThreadDomain48 td = new ThreadDomain48();

        Runnable readRunnable = new Runnable() {

            public void run() {

                td.read();

            }

        };

        Thread t0 = new Thread(readRunnable);

        Thread t1 = new Thread(readRunnable);

        t0.start();

        t1.start();

    }

看一下运行结果:

Thread-0获得了读锁, 时间为1444019668424

Thread-1获得了读锁, 时间为1444019668424

尽管方法加了锁,还休眠了10秒,但是两个线程还是几乎同时执行lock()方法后面的代码,看时间就知道了。说明lock.readLock()读锁可以提高程序运行效率,允许多个线程同时执行lock()方法后面的代码

写和写互斥

再证明一下第二句话"写和写之间互斥",类似的证明方法:

public class ThreadDomain48 extends ReentrantReadWriteLock {

        public void write() {

            try {

                writeLock().lock();

                System.out.println(Thread.currentThread().getName() + "获得了写锁, 时间为" +

                    System.currentTimeMillis());

                Thread.sleep(10000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            } finally {

                writeLock().unlock();

            }

        }

    }

    public static void main(String[] args) {

        final ThreadDomain48 td = new ThreadDomain48();

        Runnable readRunnable = new Runnable() {

            public void run() {

                td.write();

            }

        };

        Thread t0 = new Thread(readRunnable);

        Thread t1 = new Thread(readRunnable);

        t0.start();

        t1.start();

    }

看一下运行结果:

Thread-0获得了写锁, 时间为1444021393325

Thread-1获得了写锁, 时间为1444021403325

从时间上就可以看出来,10000ms即10s,和代码里一致,证明了读和读之间是互斥的

读和写互斥

最后证明一下第三句话"读和写之间互斥",证明方法无非是把上面二者结合起来而已,看一下:

public class ThreadDomain48 extends ReentrantReadWriteLock {

        public void write() {

            try {

                writeLock().lock();

                System.out.println(Thread.currentThread().getName() + "获得了写锁, 时间为" +

                    System.currentTimeMillis());

                Thread.sleep(10000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            } finally {

                writeLock().unlock();

            }

        }

        public void read() {

            try {

                readLock().lock();

                System.out.println(Thread.currentThread().getName() + "获得了读锁, 时间为" +

                    System.currentTimeMillis());

                Thread.sleep(10000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            } finally {

                readLock().unlock();

            }

        }

    }

    public static void main(String[] args) {

        final ThreadDomain48 td = new ThreadDomain48();

        Runnable readRunnable = new Runnable() {

            public void run() {

                td.read();

            }

        };

        Runnable writeRunnable = new Runnable() {

            public void run() {

                td.write();

            }

        };

        Thread t0 = new Thread(readRunnable);

        Thread t1 = new Thread(writeRunnable);

        t0.start();

        t1.start();

    }

看一下运行结果:

Thread-0获得了读锁, 时间为1444021679203

Thread-1获得了写锁, 时间为1444021689204

从时间上看,也是10000ms即10s,和代码里面是一致的,证明了读和写之间是互斥的。注意一下,"读和写互斥"和"写和读互斥"是两种不同的场景,但是证明方式和结论是一致的,所以就不证明了。

synchronized和ReentrantLock的对比

到现在,看到多线程中,锁定的方式有2种:synchronized和ReentrantLock。两种锁定方式各有优劣,下面简单对比一下:

1、synchronized是关键字,就和if...else...一样,是语法层面的实现,因此synchronized获取锁以及释放锁都是Java虚拟机帮助用户完成的;ReentrantLock是类层面的实现,因此锁的获取以及锁的释放都需要用户自己去操作。特别再次提醒,ReentrantLock在lock()完了,一定要手动unlock()

2、synchronized简单,简单意味着不灵活,而ReentrantLock的锁机制给用户的使用提供了极大的灵活性。这点在Hashtable和ConcurrentHashMap中体现得淋漓尽致。synchronized一锁就锁整个Hash表,而ConcurrentHashMap则利用ReentrantLock实现了锁分离,锁的只是segment而不是整个Hash表

3、synchronized是不公平锁,而ReentrantLock可以指定锁是公平的还是非公平的

非公平锁性能高于公平锁性能的原因:

在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。

假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。

当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。

非公平锁与非公平锁的创建

- 非公平锁:ReentrantLock()或ReentrantLock(false)

final ReentrantLock lock = new ReentrantLock();

- 公平锁:ReentrantLock(true)

final ReentrantLock lock = new ReentrantLock(true)

4、synchronized实现等待/通知机制通知的线程是随机的,ReentrantLock实现等待/通知机制可以有选择性地通知

5、和synchronized相比,ReentrantLock提供给用户多种方法用于锁信息的获取,比如可以知道lock是否被当前线程获取、lock被同一个线程调用了几次、lock是否被任意线程获取等等

总结起来,我认为如果只需要锁定简单的方法、简单的代码块,那么考虑使用synchronized,复杂的多线程处理场景下可以考虑使用ReentrantLock。当然这只是建议性地,还是要具体场景具体分析的。

最后,查看了很多资料,JDK1.5版本只有由于对synchronized做了诸多优化,效率上synchronized和ReentrantLock应该是差不多。文章来源地址https://www.toymoban.com/news/detail-408206.html

到了这里,关于Java多线程:读写锁和两种同步方式的对比的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包