欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中Synchronized与ReentrantLock的不同以及ReentrantLock的使用

程序员文章站 2022-05-04 21:00:12
...

首先,ReentrantLock是在Java1.5中被加入的,所以在之前的Java版本中是不存在 ReentrantLock的。ReentrantLock是Java并发包中非常有用的一个类,比如在ConcurrentHashMap中就用到了ReentrantLock。ReentrantLock类有两个重要的特征,其中一个是当我们尝试获取锁时,我们能够中断这个获取锁的行为;另一个是我们能够指定一个获取锁的等到时间。这两个特征对于构建高响应性以及可伸缩性的系统非常重要。简单的说,ReentrantLock继承了synchronized关键字的功能,但是又提供了更多控制锁的行为。以下源码均来自Java8
目录:
[toc]

什么是ReentrantLock

查看ReentrantLock的源代码,我们可以看到ReentrantLock实现了Lock接口以及java.io.Serializable接口。ReentrantLock是一种互斥锁,类似于Java关键字synchronized提供的隐式锁。但是,ReentrantLock提供了 一些扩展功能如公平性,这个功能能够用于为最长等待的线程提供锁定。获取锁是通过调用lock方法,直到线程调用了unlock方法才释放锁。当我们创建ReentrantLock时,可以提供一个公平参数。
ReentrantLock类似于隐式锁定提供了相同的可见性和顺序保证,也就是unlock方法一定发生在另外一个线程调用lock方法之前。

隐式锁:隐式获取锁,synchronized是它的代表,使用者不需要关心内部锁的获取和释放,锁的相关操作都由具体的关键字完成
显示锁:显示的获取锁,Lock是其代表,需要使用者在使用的时候显示地获取和释放锁

ReentrantLock和synchronized的区别

尽管ReentrantLock像隐式锁一定提供了相同的可见性和顺序保证,但是ReentrantLock提供了更多的功能并且在一些方面跟synchronized是不同的。就如前面所说,ReentrantLock与synchronized主要的区别是在获取锁的中断操作能力以及等待获取锁的时间上。
1. 公平性(fairness)。synchronized不支持公平性。一旦锁被释放了,任何线程都能获得锁,不能指定哪个线程能够优先获取锁;另一方面,通过在创建ReentrantLock实例的时候我们可以指定公平属性。在竞争锁的过程中,通过指定公平性,我们能够为最长等待线程(即等待此锁最长的线程)提供锁。
ReentrantLock有两个构造函数,如下代码,可见无参数的构造函数是非公平性的。
Java中Synchronized与ReentrantLock的不同以及ReentrantLock的使用
2. 第二个主要的区别是tryLock方法。ReentrantLock提供了tryLock方法,当且仅当锁是可用的并且没有被其他的线程持有tryLock方法才能获取此锁。这减轻了线程在等待锁时产生的阻塞。
3. ReentrantLock在等待获取锁的过程中提供了中断,即我们能够中断获取锁的过程。在synchronized中,一个线程在等到锁的过程中会一直阻塞,如果陷入了无限等待中,我们是没有办法控制这个过程的。而ReentrantLock提供了一个lockInterruptibly方法,当线程正在等待获取锁的过程中,我们能够用此方法来中断线程。同时我们也可以给tryLock指定一个超时时间,在这个时间范围内如果没有获得锁,则返回false。

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
  1. ReentrantLock提供了获取等到锁的所有线程,如getQueuedThreads()方法

ReentrantLock的优势

其实在上一个小节中已经部分说明ReentrantLock 的优势,这里做一个总结
1. 能够中断获取锁过程
2. 在等待锁时可以指定超时时间
3. 能够创建公平锁
4. 获取所有等待锁的线程
5. 可以灵活的尝试获取锁而不锁定线程

ReentrantLock的劣势

凡事都有两面性,说完ReentrantLock的优势,再来说说ReentrantLock的劣势。ReentrantLock的主要缺点是使用ReentrantLock时必须将ReentrantLock中的方法放到try-finally结构中,这就造成了代码不易读并且隐藏了业务逻辑。尽管类似于Eclipse及Netbeans的IDE能够为我们添加try catch块,但是代码仍然显得有点乱。另一个缺点是,使用ReentrantLock之后,类的获取以及释放全部由程序员负责,这虽然为我们编写代码提供了更多功能,但是也带来了新的bug,特别是我们忘记在finally块中释放锁。有点类似于C++中的free与delete一定要配对使用。

Java中使用ReentrantLock及Lock的例子

代码非常简单,就是两个线程分别修改count的值并且输出到标准输入上。getCount用ReentrantLock加锁,而getCountTwo用synchronized关键字加锁。从代码中可以看到采用synchronized关键字的代码更加易读,但是相对于ReentrantLock没有那么灵活。

package com.ll.concurrent;

import java.util.concurrent.locks.ReentrantLock;

/**
* @describe 如何使用ReentrantLock类
* @author liang
* @date 创建时间:2017年6月10日
*
*/
public class ReentrantLockHowto {
    //一个非公平的ReentrantLock
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;
    //在lock上获取锁,在finally中释放锁
    public int getCount() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " getCount方法获取count的值:" + count);
            return count++;
        } finally {
            lock.unlock();
        }
    }
    //采用synchronized隐式加锁
    public synchronized int getCountTwo() {
        System.out.println(Thread.currentThread().getName() + " getCountTwo方法获取count的值: " + count);
        return count++;
    }
    public static void main(String args[]) {
        final ReentrantLockHowto counter = new ReentrantLockHowto();
        Thread t1 = new Thread() {
            public void run() {
                while (counter.getCount() < 6) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                while (counter.getCount() < 6) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        t1.start();
        t2.start();
    }
}

代码在本人github,其中说明了java中一些类的使用方式,欢迎start。

参考链接

ReentrantLock Example in Java, Difference between synchronized vs ReentrantLock