ReentrantLock是重入锁,它与synchronized很像,它是synchronized的加强版,因为它具有一些synchronized没有的功能。
下面我们看看两者的区别:synchronized具有一定的局限性:- 当线程尝试获取锁的时候,如果获取不到锁会一直阻塞;
- 如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待;
- 是非公平的。
而ReentrantLock实现了AQS,可以完成下列功能:
- 可中断响应;
- 锁申请等待限时;
- 公平锁;
- 与Condition一起使用,实现synchronized与wait/notify的功能。
引入几个概念:
提到ReentrantLock,我们不得不明白几个概念:- 可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。
- 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,ReentrantLock则提供了中断功能。
- 公平锁与非公平锁。公平锁是指多个线程必须按顺序,不许插队。非公平锁允许插队。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。
- CAS,在前面已经提到。
使用示例
具体用法通过简单代码通过代码演示:import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo implements Runnable { static ReentrantLock lock = new ReentrantLock(); static int count = 0; @Override public void run() { for (int i = 0; i < 10000; i++) { lock.lock(); try { count++; } finally { lock.unlock(); } } } public static void main(String[] args) { ReentrantLockDemo r = new ReentrantLockDemo(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count); //结果输出:20000 }}
通过ReentrantLock解决死锁问题:
import java.util.concurrent.locks.ReentrantLock;public class KillDeadlockDemo implements Runnable { static ReentrantLock lock1 = new ReentrantLock(); static ReentrantLock lock2 = new ReentrantLock(); int lock; public KillDeadlockDemo(int lock) { super(); this.lock = lock; } @Override public void run() { try { if (lock == 1) { lock1.lockInterruptibly(); Thread.sleep(500); lock2.lockInterruptibly(); } else { lock2.lockInterruptibly(); Thread.sleep(500); lock1.lockInterruptibly(); } } catch (InterruptedException e) { } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); } System.out.println(Thread.currentThread().getName() + "退出!"); } } public static void main(String[] args) { KillDeadlockDemo kdd1 = new KillDeadlockDemo(1); KillDeadlockDemo kdd2 = new KillDeadlockDemo(2); Thread t1 = new Thread(kdd1); Thread t2 = new Thread(kdd2); t1.start(); t2.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t2.interrupt(); /* *结果输出: *Thread-1退出! *Thread-0退出! */
}
}
使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待,也可以解决死锁问题:
import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class TryLockDemo implements Runnable { static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(1, TimeUnit.SECONDS)) { Thread.sleep(1100); } else { System.out.println(Thread.currentThread().getName() + "获取锁失败!释放"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public static void main(String[] args) { TryLockDemo td = new TryLockDemo(); Thread thread1 = new Thread(td); Thread thread2 = new Thread(td); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } //结果输出:Thread-1获取锁失败!释放 }}
公平锁演示:
import java.util.concurrent.locks.ReentrantLock;public class FairLockDemo implements Runnable { static ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " get lock"); Thread.sleep(1000); } catch (InterruptedException e) { } finally { lock.unlock(); } } } public static void main(String[] args) { FairLockDemo fld = new FairLockDemo(); Thread t1 = new Thread(fld); Thread t2 = new Thread(fld); Thread t3 = new Thread(fld); t1.start(); t2.start(); t3.start();/**Thread-0 get lock*Thread-1 get lock*Thread-2 get lock*Thread-0 get lock*Thread-1 get lock*Thread-2 get lock*..........*/ }}
浅谈原理
ReentrantLock使用到了AQS,AQS的全称为AbstractQueuedSynchronizer,这个类也是在java.util.concurrent.locks下面。这个类似乎很不容易看懂,因为它仅仅是提供了一系列公共的方法,让子类来调用。
先以ReentrantLock排它锁为例开始展开讲解如何利用AQS的。
ReentrantLock的构造方法有两个,如下图所示:对象中有一个属性叫sync,有两种不同的实现类,默认是“NonfairSync”非公平锁来实现,而另一个“FairSync”公平锁它们都是排它锁的内部类,不论用那一个都能实现排它锁,只是内部可能有点原理上的区别。先以“NonfairSync”类为例,它的lock()方法
lock()方法先通过CAS尝试将状态从0修改为1。若直接修改成功,前提条件自然是锁的状态为0,则直接将线程的OWNER修改为当前线程。若上一个动作未成功,则会间接调用了acquire(1)来继续操作,这个acquire(int)方法就是在AbstractQueuedSynchronizer当中了。首先获取这个锁的状态,如果状态为0,则尝试设置状态为传入的参数(这里就是1),若设置成功就代表自己获取到了锁,返回true了。状态为0设置1的动作在外部就有做过一次,内部再一次做只是提升概率,而且这样的操作相对锁来讲不占开销。
○ 如果状态不是0,则判定当前线程是否为排它锁的Owner,如果是Owner则尝试将状态增加acquires(也就是增加1),如果这个状态值越界,则会抛出异常提示,若没有越界,将状态设置进去后返回true.如果状态不是0,且自身不是owner,则返回false。