Java并发-显式锁篇【可重入锁+读写锁】

个人博客:javalover.cc

前言

在前面并发的开篇,我们介绍过内置synchronized;

这节我们再介绍下显式Lock

显式锁包括:可重入锁ReentrantLock、读写锁ReadWriteLock

关系如下所示:

image-20210523174802931

简介

显式锁和内置锁最大的区别就是:显式锁需手动获取锁和释放锁,而内置锁不需要

关于显式锁,本节会分别介绍可它的实现类 - 可重入锁,以及它的相关类 - 读写锁

可重入锁,实现了显式锁,意思就是可重入的显式锁(内置锁也是可重入的)

读写锁,将显式锁分为读写分离,即读读可并行,多个线程同时读不会阻塞(读写,写写还是串行)

下面让我们开始吧

文章如果有问题,欢迎大家批评指正,在此谢过啦

目录

可重入锁 ReentrantLock

读写锁 ReadWriteLock

区别

正文 1.可重入锁 ReentrantLock

我们先来看下它的几个方法:

public ReentrantLock();构造函数,默认构造非公平的锁(可插队,如果某个线程获取锁时,刚好锁被释放,那么这个线程就会立马获得锁,而不管队列里的线程是否在等待)

public void lock():获取锁,以阻塞的方式(如果其他线程持有锁,则阻塞当前线程,直到锁被释放);

public void lockInterruptibly() throws InterruptedException:获取锁,以可被中断的方式(如果当前线程被中断,则抛出中断异常);

public boolean tryLock(): 尝试获取锁,如果锁被其他线程持有,则立马返回false

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException:尝试获取锁,并设置一个超时时间(如果超过这个时间,还没获取到锁,则返回false)

public void unlock(): 释放锁

首先我们先看下它的构造方法,内部实现如下:

public ReentrantLock() { sync = new NonfairSync(); }

可以看到,这里创建了一个非公平锁

公平锁:如果获取锁时,被其他线程持有,则将当前线程放入等待队列

非公平锁:如果获取锁时,刚好锁被释放,那么这个线程就会立马获得锁,而不管队列里的线程是否在等待

非公平锁的好处就是,可以减少线程的挂起和唤醒开销

如果某个线程的执行任务所需时间很短,甚至比唤醒队列中的线程所消耗的时间还短,那么非公平锁的优势就很明显

我们可以假设这样一个情景:

线程A的任务执行耗时为10ms

而唤醒队列中的线程B到执行真正去执行线程B的任务耗时为20ms

那么当线程A去获取锁时,刚好锁又被释放,此时线程A抢先获得锁,并执行任务,然后释放锁

当线程A释放锁之后,队列中当线程B才被唤醒正要去获取锁,那么线程B被唤醒的这段时间CPU就没有被浪费,从而提高了程序的性能

这也是为啥默认是非公平锁的原因(一般情况下,非公平锁的性能高于公平锁)

那什么时候应该用公平锁呢?

持有锁的时间较长,即线程的任务执行耗时较长

请求锁的时间间隔较长

因为这种情况下,如果线程插队获取到锁,结果任务还半天执行不完,那么队列中被唤醒的线程醒来发现锁还是被占有的,就会被再次放到队列中(此时并不会提高性能,还有可能降低)

接下来我们看下关键的部分:获取锁

获取锁有多个方法,我们用代码来看下他们之间的区别

先来看下lock()方法,示例代码如下:

public class ReentrantLockDemo { private Lock lock = new ReentrantLock(); private int i = 0; public void add(){ lock.lock(); try { i++; }finally { System.out.println(i); lock.unlock(); } } public static void main(String[] args) throws InterruptedException { ReentrantLockDemo demo = new ReentrantLockDemo(); ExecutorService service = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { service.submit(()->{ demo.add(); }); } } }

依次输出1~100,这是因为lock()获取锁时,会以阻塞的方式来获取

接下来看下 tryLock()方法,代码如下:

public class ReentrantLockDemo { private Lock lock = new ReentrantLock(); private int i = 0; public void tryAdd(){ if(lock.tryLock()){ try { i++; }finally { System.out.println(i); lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { ReentrantLockDemo demo = new ReentrantLockDemo(); ExecutorService service = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { service.submit(()->{ demo.tryAdd(); }); } } }

运行发现,输出永远都少于100,是因为tryLock()如果获取锁失败,会立马返回false,而不是阻塞等待

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygjzy.html