并发第八弹 ReentrantLock与synchronized的比较

并发第六弹 AQS抽象队列同步器 中已经介绍了AQS,但AQS毕竟是抽象的,是需要实现的,今天就来介绍AQS极为重要的一种实现:独占锁ReentrantLock。

本文主要介绍ReentrantLock与synchronized的比较以及ReentrantLock的基本使用。

ReentrantLock与synchronized的比较

ReentrantLock是Java代码层面的锁,而synchronized关键字是Java在JVM层面的锁,这两种锁经常拿来一起比较,包括它们的使用场景。

在对它们进行比较之前,先来了解一下几种锁的思想,锁的思想并非实际的锁,也不局限于Java领域。

可重入锁
  • 假如一把锁锁了n个地方,那么只要得到这把锁,那n个地方都可以访问;

公平锁、非公平锁
  • 公平锁是指多个线程按照申请锁的顺序来获取锁,非公平锁性能更高,因为公平锁需要在多核的情况下维护一个队列;

分段锁
  • ConcurrentHashMap是学习分段锁的最好实践,ConcurrentHashMap默认的并发级别是16,即内部分成了16个HashMap,当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode知道它将放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行插入;

乐观锁、悲观锁
  • 乐观锁适用于读多场景,悲观锁适用于写多场景。乐观锁常见的两种实现方式:版本号机制或CAS算法实现;

共享锁、独占锁
  • 独占模式下每次只能有一个线程持有锁,属于悲观锁的思想,独占锁限制了并发能力,因为读读操作不需要加锁;

  • 共享锁允许多个线程同时获取锁,并发访问共享资源,如ReadWriteLock的读锁是共享锁,共享锁是一种乐观锁思想;

自旋锁
  • 尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU;


ReentrantLock与synchronized的比较:
  • synchronized锁是一个内置关键字,synchronized更加简单;而ReentrantLock是一个Java类,提供了丰富的API,使用起来更加灵活;

  • synchronized有一个锁升级的过程:无锁->偏向锁->轻量级锁(CAS加锁)->膨胀为重量级锁;

  • synchronized自动释放锁(代码执行完或发生异常),ReentrantLock需要手动释放锁

  • synchronized是非公平锁,synchronized可公平可不公平

  • synchronized通过Object类的wait和notify方法实现线程同步,ReentrantLock通过条件变量Condition实现线程同步;

  • synchronized不可中断,ReentrantLock可中断;

  • 两者都可重入;

  • 两者都是独占锁、悲观锁思想;


ReentrantLock与synchronized的性能比较与使用场景:
  • synchronized适用于少量代码的同步问题,ReentrantLock适用于大量代码的同步问题;

  • 资源竞争不激烈时synchronized性能要略优于ReentrantLock;资源竞争激烈时,ReentrantLock性能要优与ReentrantLock

  • synchronized经历了各种优化,性能已经和ReentrantLock差不多了;


Lock接口

ReentrantLock实现了Lock接口,相较于synchronizedLock功能更加丰富,使用起来也灵活,除了提供与synchronized一样基本的锁定之外,还提供如下特有功能:
  • 非阻塞尝试获取锁tryLock()

  • 获取可被中断的锁lockInterruptibly()

  • 设置超时时间尝试获取锁tryLock(long, TimeUnit)


Lock接口的完整方法如下:

  • lock():获得锁,如果获取不到就阻塞,获取得到就立即返回,不能被打断;

  • void lockInterruptibly():与lock方法基本一样,但是可以被interrupt打断;

  • Condition newCondition():返回一个新Condition绑定到该Lock实例;

  • boolean tryLock():会立即返回true或false,注意,该方法会获取锁;

  • boolean tryLock(long time, TimeUnit unit):与无参的tryLock方法一样,只不过多了一个超时时间;

  • void unlock():释放锁; 



ReentrantLock的基本使用

注意:
  • lock方法要写在try-catch块外面。lock方法与try之间不要有代码,如果中间有代码且抛出异常,则会导致无法释放锁;



public class ReentrantLockTest {
    private int count;
    private ReentrantLock reentrantLock = new ReentrantLock();

    public void addByLock(){
        reentrantLock.lock();
        try {
            count++;
        }finally {
            reentrantLock.unlock();
        }
    }

    public void addByTryLock() {
        // 拿不到锁就自旋
        while (!reentrantLock.tryLock()){
        }
        try {
            count++;
        }finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockTest rlt = new ReentrantLockTest();
        for (int i = 0; i < 1000000; i++) {
            new Thread(()->{
                rlt.addByTryLock();
            }).start();
        }
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println(rlt.count);
        }));
    }
}



原文始发于微信公众号(初心JAVA):并发第八弹 ReentrantLock与synchronized的比较

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35531.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!