在没有接触ReentrantLock锁之前我们一直使用的是Synchronized互斥锁。Synchronized是Java的一个关键字,锁是由Jvm实现的,因此我们在使用的时候相对比较方便(无需考虑释放锁),但是synchronized相对于我们今天介绍的ReentrantLock有以下的局限性:
1.当线程尝试获取锁的时候,若不能成功获取锁便会一直阻塞且用户无法控制
2.如果获取锁的线程休眠或者阻塞了,若不发生异常则将导致需要该锁的线程一直等待下去
了解ReentrantLock锁前了解一些概念
1.可重入锁:线程获取对象的锁之后,还可以获取该对象的锁。
即同一线程可以多次获取一个对象的锁。
synchronized和Reentrant都是可重入锁。
2.可中断锁:线程在尝试获取锁的时候,是否有线程可以选择中断
synchronized是不可中断锁,ReentrantLock是可中断锁
3.公平锁与非公平锁:公平锁是当多个线程尝试获取同一把对象锁时,按照申请锁
的先后顺序进行获取锁。不存在插队情况
synchronized是非公平锁
ReentrantLock可以实现公平锁和非公平锁(默认是非公平锁,可以通过构造设置为公平锁)
ReentrantLock的基本使用
它有两个构造函数,我们可以使用带boolean类型的参数来进行创建一个公平锁的实例
举例
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
public static int num = 0;
public static ReentrantLock lock = new ReentrantLock();
public static void add(){
lock.lock(); //获取锁
try{
num++;
}finally {
lock.unlock(); //释放锁
}
}
public static void main(String[] args) {
ThreadA t1 = new ThreadA();
ThreadA t2 = new ThreadA();
ThreadA t3 = new ThreadA();
t1.start();
t2.start();
t3.start();
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ReentrantLockTest.num);//打印结果
}
}
class ThreadA extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
ReentrantLockTest.add();
}
}
}
ReentrantLock的基本使用过程:
1.创建对象
ReentrantLock lock = new ReentrantLock();
2.获取锁
lock.lock();
3.释放锁
lock.unlock();
注意:我们在释放锁的时候一定要将lock.unlock()方法放在finally代码块中,防止因为异常导致锁不能释放导致其它线程获取不到锁对象。
ReentrantLock是可重入锁
public static void add(){
lock.lock();//获取锁
lock.lock(); //尝试再次获取锁
try{
num++;
}finally {
lock.unlock(); //释放锁
lock.unlock(); //再次释放锁
}
}
上述我们已经介绍可重入锁了,这和栗子就表明ReentrantLock是可重入锁
我们知道synchronized和ReentrantLock都是可重入锁。每次获取锁的时候,锁计数器+1,每次释放锁的时候锁计数器减1.只有锁计数器减到0才真正的释放锁。因此lock和unlock一定是成对存在的
注意:lock和unlock要成对存在!unlock还是要放在finally代码块中
ReentrantLock实现公平锁
这个栗子用到了公平锁
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
public static int num = 0;
public static ReentrantLock lock = new ReentrantLock(true);
public static void add(){
lock.lock();
try{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ThreadA t1 = new ThreadA("A");
t1.start();
ThreadA t2 = new ThreadA("B");
t2.start();
ThreadA t3 = new ThreadA("C");
t3.start();
}
}
class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
ReentrantLockTest.add();
}
}
}
公平锁最大的特点就是不会产生饥饿,每个线程获取锁是按照先后顺序的。
ReentrantLock获取锁的过程是可以中断的
想象一个synchronized在获取锁的时候有两种状态。
1.要么没有获取到锁,一直等待。
2.要么获取到锁,执行后续代码。
然而ReentrantLock可以实现更为人性化的功能。在尝试获取锁到还未获取锁的时间内可以被中断,即在等待锁的过程中可以被告知不用获取锁。
举例子:
张三和李四约会,李四因为有事不能来了,那么因为张三不知道所以就会一直等待下去(类似用synchronized修饰,获取不到锁就会一直等待)另一种人性化的解决方案就是李四不能来了并通知了张三,这样张三就没必要一直等待下去了。是不是第二种方案更加人性化
代码演示:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynchronizedTest2 {
public static void main(String[] args) {
ReentrantLock reentrantLock1 = new ReentrantLock();
ReentrantLock reentrantLock2 = new ReentrantLock();
T t1 = new T("线程1",1,reentrantLock1,reentrantLock2);
T t2 = new T("线程2",2,reentrantLock1,reentrantLock2);
t1.start();
t2.start();
}
}
class T extends Thread {
int i;
protected ReentrantLock lock1;
protected ReentrantLock lock2;
public T(String name, int i, ReentrantLock lock1, ReentrantLock lock2) {
super(name);
this.i = i;
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
if(this.i == 1){
//lockInterruptibly()如果当前线程未被中断则获取锁
lock1.lockInterruptibly();
Thread.sleep(1000);
lock2.lockInterruptibly();
}else{
lock2.lockInterruptibly();
Thread.sleep(1000);
lock1.lockInterruptibly();
}
}catch (Exception e){
System.out.println("触发异常,此时的中断标志为"+this.isInterrupted());
}finally {
//判断当前线程是否获取锁
if(lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
}
上述代码会发生死锁,因为线程1首先执行的话它在执行到lockInterruptibly()的时候判断当前线程不是中断线程那么就会尝试获取lock1的锁。获取锁后进行休眠,此时线程2执行后续的代码时同理会获取到lock2的锁。休眠结束后线程1要尝试获取lock2的锁,同时线程2要获取lock1的锁。但是线程1占用了lock1的锁,线程2占用了lock2的锁,因此会发生死锁。
ReentrantLock获取锁的中断。
上述代码进行改进:
public static void main(String[] args) {
ReentrantLock reentrantLock1 = new ReentrantLock();
ReentrantLock reentrantLock2 = new ReentrantLock();
T t1 = new T("线程1",1,reentrantLock1,reentrantLock2);
T t2 = new T("线程2",2,reentrantLock1,reentrantLock2);
t1.start();
t2.start();
try {
Thread.sleep(5000);
t2.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//分析
/*上述代码在主线程中休眠5秒后将线程2设置为中断线程此时线程2的中断
标记为true,此时lockinterruptibly将会抛出异常。此时finally代码中
执行,将释放掉获取的锁,此时线程1正常获取锁,死锁就解决了。
此时程序可以正常结束:
分析:lockInterruptibly方法
lockInterruptibly该方法在使用时如果当前线程已经设置过中断标志或者在获取锁的同时被中断则会抛出interruptException异常,并将中断标志清除设为false
ReentrantLock锁申请限制时间
回忆一下synchronized尝试获取锁时会一直等待,等待的时间有长有短不能干预。而ReentrantLock可以设置锁申请的超时时间。
看一下方法
boolean tryLock(); //锁未被其它线程占有的情况下才获取锁
如果当前锁没有被其它线程保持的话,他会立即返回true,获取锁。
如果当前线程已经保持该锁则将计数器加1
如果锁被另一个线程保持,则返回false
boolean tryLock(long timeout, TimeUnit unit);//锁在给定时间内没有被其它线程保持,并没有中断则尝试获取锁
三种情况:
1.在指定时间内可以获取到锁,则返回true,将锁的计数器加1
2.在进入此方法时线程中断标志为true或在等待的时候被中断,则会抛出异常。并清除已中断标志
3.超时还未获取锁,就返回false.
//在这三个方法执行前,当前线程都将处于休眠状态
代码演示:
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Tt t1 = new Tt("线程1",lock);
Tt t2 = new Tt("线程2",lock);
t1.start();
t2.start();
}
}
class Tt extends Thread{
private ReentrantLock lock;
public Tt(String name,ReentrantLock lock){
super(name);
this.lock = lock;
}
@Override
public void run() {
System.out.println(System.currentTimeMillis()+":"+Thread.currentThread().getName()+"此时尝试去获取锁");
if(lock.tryLock()){
//tryLock()方法会立即返回是否获取到锁
System.out.println(System.currentTimeMillis()+":"+Thread.currentThread().getName()+"成功获取到锁");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println(System.currentTimeMillis()+":"+Thread.currentThread().getName()+"不能获取锁");
}
}
}
我们可以看到tryLock会立即相应是否可以获取到锁
有参数的tryLock
lock.tryLock(7,TimeUnit.SECONDS)
结果: