并发编程系列之ReentrantLock用法简介
1、什么是ReentrantLock?
ReentrantLock是实现底层的Lock接口的可重入锁实现。支持公平锁模式和非公平锁模式。
典型例子:
代码语言:javascript复制ReentrantLocl rlock = new ReentrantLock();
try {
rlock.lock();
// 业务处理
} finally {
rlock.unlock();
}
2、什么是重入锁和不可重入锁?
- 可重入锁:又称之为递归锁,也就是一个线程可以反复获取锁多次,一个线程获取到锁之后,内部如果还需要获取锁,可以直接再获取锁,前提是同一个对象或者class。ReentrantLock和synchronized都是可重入锁,可重入锁的最重要作用就是避免死锁的情况。
- 不可重入锁:又称之为自旋锁,底层是一个循环加上unsafe和cas机制,就是一直循环直到抢到锁,这个过程通过cas进行限制,如果一个线程获取到锁,cas (compareAndSet)会返回1,其它线程包括自己就不能再持有锁,需要等线程释放锁。
ReentrantLock是可重入锁,所以允许一个线程多次获取资源锁。第一次调用lock时,计数设置为1,再次获取资源锁时加1,调用unlock解锁,计数减1,直到减为0,释放锁资源,如图所示:
3、公平锁和非公平锁的区别?
- 公平锁:多个线程按照申请锁的顺序去获取锁,线程会在队列里排队,按照顺序去获取锁。只有队列第1个线程才能获取到锁,获取到锁之后,其它线程都会阻塞等待,等到持有锁的线程释放锁,其它线程才会被唤醒。
- 非公平锁:多个线程都会去竞争获取锁,获取不到就进入队列等待,竞争得到就直接获取锁;然后持有锁的线程释放锁之后,所有等待的线程就都会去竞争锁。
代码语言:javascript复制synchronized是非公平锁,ReentrantLock可以支持非公平锁和公平锁,new ReentrantLock(true),加上个true就是公平锁,默认情况是非公平锁
// 设置ReentrantLock为公平锁
ReentrantLock lock = new ReentrantLock(true);
4、ReentrantLock方法
在idea编辑器里查看ReentrantLock的方法:
挑一些常用方法进行描述
lock()
:如果共享资源最初是空闲的,调用lock会进行计数加1,并将锁提供给线程。会进行累加unlock()
:调用unlock方法,会进行计数减1,减为0时,释放资源锁tryLock()
:为了避免锁竞争,可以使用tryLock
,如果资源没有被其他线程持有,会返回true,加锁成功,已经被其他线程持有了,返回false,加锁失败tryLock(long timeout, TimeUnit unit)
:这个尝试加锁方法,多了一个超时时间lockInterruptably()
:如果资源空闲,则此方法获取锁,允许该线程在获取资源时被其他线程中断。如果该线程在获取锁过程,其它线程抢进来,获取锁过程会被中断并立即返回而不获取锁getHoldCount()
:此方法返回资源上持有的锁数的计数。isHeldByCurrentThread()
:如果当前线程持有资源锁,则此方法返回true
5、ReentrantLock例子
例子:进行大数据的count统计,开启20个线程,如何保证线程安全,计数的正确性?
- volatile保证变量可见性
- CountDownLatch协同多个线程
- ReentrantLock 保证线程安全
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockCountExample {
private static volatile int count = 0;
static ReentrantLock lock = new ReentrantLock();
public static void countHandler() {
lock.lock();
try {
count ;
}finally {
lock.unlock();
}
}
public static void doCountConcurrent() throws InterruptedException {
int threads = 20;
CountDownLatch cdl = new CountDownLatch(threads);
for (int i = 0; i < threads; i ) {
new Thread(() -> {
for (int n = 0; n < 10000; n ) {
countHandler();
}
cdl.countDown();
}).start();
}
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
doCountConcurrent();
System.out.println("统计耗时:" (System.currentTimeMillis() - start) "ms");
System.out.println("result:" ReentrantLockCountExample.count);
}
}