一、重入锁的定义:
为什么会叫重入锁,顾名思义,表示这个锁可以返回被添加,就是一个线程可以多次获得一把锁,只要在最后的时候做相同次数的锁释放即可。
Lock lock = new ReentrantLock();
lock.lock();
lock.lock();
try {
//业务代码
} finally {
lock.unlock();
lock.unlock();
}
二、重入锁和synchronized的区别
首先java在一开始时,可以用synchronized来解决并发过程中数据安全问题,那为什么还需要使用Lock锁呢?有的人可能会认为是性能问题:认为ReentrantLock锁的性能优于synchronized。其实这种想法是错误的,确实在JDK6.0之前,synchronized的性能是远不如重入锁的性能的,但在JDK6.0版本对synchronized做了大量的优化,使得两者的性能差异并不大。所有java为什么会重复造轮子,引入重入锁呢?其重要原因有三点:
1.支持中断响应:对于synchronized来说,一个线程如果在等待获得锁资源,那么只有两种可能,获得这个锁资源继续向下执行,或者没有得到锁资源继续等待下去。但是重入锁,给我提供了另外一种机制,那就是线程在等待的时候,可以根据需求取消对锁的请求。这样就可以很好的解决线程的死锁问题:例如一个线程(Thread-1)在获得锁A的资源后,当再去获得锁B的资源时,没有获得锁B的资源,从而进入等待状态,但这时这个线程得到的锁A资源不释放,如果这时另一个线程(Thread-2)已经获得了锁B,去请求锁A时,请求不到资源,这时就会进入死锁状态。如果使用的是重入锁,就可以给线程(Thread-1或者Thread-2)一个中断响应,那么这个线程对应的获得的锁资源也会被释放掉,从而避免死锁的发生。
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public IntLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
//产生死锁
if (lock == 1) {
lock1.lockInterruptibly();//可以对中断进行响应的加锁方式
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
Thread.sleep(500);
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getId()+":线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
//已经死锁
Thread.sleep(1000);
//通过中断其中一个线程,使其释放获得锁资源,从而结束死锁
t2.interrupt();
}
2.支持超时:一个线程在请求锁资源时,可以设置一个超时时间,一但超过这个超时时间,线程不会一直处在等待状态,而是抛出一个异常,并可以释放掉曾经持有的资源。
// 支持超时的 API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
代码实例:
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
@Override
public void run() {
try {
if (lock1.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
} else {
System.out.println("获得锁超时");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
IntLock l1 = new IntLock();
Thread t1 = new Thread(l1);
Thread t2 = new Thread(l1);
t1.start();
t2.start();
}
}
3.公平性问题:使用synchronized的加锁方式,就是一种非公平锁,也就是当一个线程释放掉自己的锁资源后。系统只是从等待的线程中,随机的选取一个执行,这样做的结果也就是会产生饥饿现象,也就是锁某个线程会一直得不多锁资源从而一直等待。但是重入锁,可以设置其属性,他有一个构造方法
public ReentrantLock(boolean fair);
其中:设置为true,表示使用公平锁,false使用非公平锁。但是使用公平锁时,需要额外维护一个有序队列,从而降低性能,因此相对想来,没有特别需求,一般都是使用的非公平锁。
4.非阻塞的获得锁:当一个线程在没有获得锁资源时,不是进入等待,而是直接返回结果,那这个线程也会释放掉曾经得到的资源。
// 支持非阻塞获取锁的 API
boolean tryLock();
代码实例:
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
@Override
public void run() {
try {
if (lock1.tryLock()){
Thread.sleep(100);
} else {
System.out.println("没有获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
IntLock l1 = new IntLock();
Thread t1 = new Thread(l1);
Thread t2 = new Thread(l1);
t1.start();
t2.start();
}