• java多线程 21 : ReentrantReadWriteLock ,synchronized和ReentrantLock的对比


    读写锁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可以指定锁是公平的还是非公平的

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

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

    6. ReentrantLock的某些方法可以决定多长时间内尝试获取锁,如果获取不到就抛异常,这样就可以一定程度上减轻死锁的可能性,如果锁被另一个线程占据了,synchronized只会一直等待,很容易错序死锁 

    7.synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大 


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

    最后,查看了很多资料,JDK1.5版本只有由于对synchronized做了诸多优化,效率上synchronized和ReentrantLock应该是差不多。

     



  • 相关阅读:
    求链表的倒数第k个节点
    打印蛇形矩阵
    数组元素前移问题(今日头条笔试题)
    单链表的节点内数据值的删除问题(携程网笔试题)
    子树判断问题(百度笔试题)
    求链表的第一个公共节点问题(好未来笔试题)
    正则表达式常用总结
    正则表达式start(),end(),group()方法
    test、exec、match区别
    matches()方法
  • 原文地址:https://www.cnblogs.com/signheart/p/ec6440b1c6a00ae5688b1f658f02ccdb.html
Copyright © 2020-2023  润新知