相对于synchronized它具备如下特点:

  • 可中断,synchronized获得锁以后只能持有,不能被其它方法中断掉,但ReentrantLock锁可以被中断,即锁可以被破坏掉
  • 可以设置超时时间,如果线程拿不到锁,按照synchronized的设置,线程就会进入EntryList中无限期的等待,直到获得锁才可以继续执行,但ReentrantLock可以设置等待时间,如果一定时间内还是竞争不到锁,即可以执行其他操作。
  • 可以设置为公平锁,用以解决饥饿问题
  • 支持多个条件变量,按照synchronized的处理方式,调用wait方法的线程会统一进入一个WaitSet中等待,相当于不管什么条件不满足都会进入WaitSet中,不去区分未满足的条件,这里的WaitSet就相当于一个条件变量。ReentrantLock则不同,可以支持多个条件变量,比如线程1因为不满足条件1而进入WaitSet1,线程2因为不满足条件2而进入WaitSet2,可以实现更细粒度的划分。


    与synchronized一样,都支持可重入。

基本语法:

  1. //获取锁
  2. reentrantLock.lock();
  3. try {
  4. //临界区
  5. }finally {
  6. //释放锁
  7. reentrantLock.unlock();
  8. }

要保证lock方法和unlock方法成对出现,这样即使出现异常也能在finally中释放掉锁,以免其它线程迟迟获不到锁。

可重入:

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁住。

  1. import java.util.concurrent.locks.ReentrantLock;
  2. public class TestReentrantLock {
  3. private static ReentrantLock lock = new ReentrantLock();
  4. public static void main(String[] args) {
  5. lock.lock();
  6. try{
  7. System.out.println("enter main");
  8. m1();
  9. }finally {
  10. lock.unlock();
  11. }
  12. }
  13. public static void m1(){
  14. lock.lock();
  15. try{
  16. System.out.println("enter m1");
  17. m2();
  18. }finally {
  19. lock.unlock();
  20. }
  21. }
  22. public static void m2(){
  23. lock.lock();
  24. try{
  25. System.out.println("enter m2");
  26. }finally {
  27. lock.unlock();
  28. }
  29. }
  30. }

这里的lock.lock()和synchronized(lock)用法等价,都是对lock对象加锁。

可打断:

可打断是指,当线程获取不到锁时,不会无限制的等待锁释放,而是可以被interrupt方法打断等待。是一种避免线程死等锁的方式,可以减少死锁的发生。
lock.lockInterruptibly()方法是可被打断的加锁方法。

  1. import com.sun.applet2.Applet2;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class TestReentrantLock {
  4. private static ReentrantLock lock = new ReentrantLock();
  5. public static void main(String[] args) throws InterruptedException {
  6. Thread t1 = new Thread(()->{
  7. try{
  8. //如果没有竞争那么此方法就会获取lock对象锁
  9. //如果有竞争就进入阻塞队列,可以被其它线程用interrupt方法打断
  10. System.out.println("尝试获取锁");
  11. lock.lockInterruptibly();
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. System.out.println("没有获得锁,返回");
  15. return;//正常退出返回
  16. }
  17. try{
  18. System.out.println("获取到锁");
  19. }finally {
  20. lock.unlock();
  21. }
  22. },"t1");
  23. lock.lock();
  24. t1.start();
  25. Thread.sleep(1000);
  26. t1.interrupt();
  27. }
  28. }

用try-catch块包裹住lock.lockInterruptibly()方法,因为有可能被interrupt方法打断,所以要放在try块中,如果确实被打断,那么则会进入catch块中报异常,同时正常退出。如果没有被打断,当竞争到锁的时候则会执行第二个try块中的内容。

打断的效果:
image.png

锁超时:

与被动的interrupt()方法打断线程等待相比,锁超时提供一种主动的避免死等的方式。
这里用到的方法是ReentrantLock对象的trylock()方法与trylock(long timeout)方法,该方法返回Boolean值,如果返回True代表获取到锁,返回False代表没有获取到锁。

trylock()方法:

  1. import com.sun.applet2.Applet2;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class TestReentrantLock {
  4. private static ReentrantLock lock = new ReentrantLock();
  5. public static void main(String[] args) throws InterruptedException {
  6. Thread t1 = new Thread(()->{
  7. System.out.println("尝试获取锁");
  8. if(!lock.tryLock()){
  9. System.out.println("获取不到锁");
  10. return;
  11. }
  12. try{
  13. System.out.println("获得到锁");
  14. }finally {
  15. lock.unlock();
  16. }
  17. },"t1");
  18. lock.lock();//主线程先一步获得锁
  19. System.out.println("获得到锁");
  20. t1.start();
  21. }
  22. }

trylock方法会尝试获取锁,如果获取到锁会正常执行,如果获取不到锁不会等待,而是会立刻不再竞争锁,从而继续执行代码。
这里的lock.lock()与lock.tryLock方法均是对一个对象上锁,可以保证互斥。

trylock(有参)方法:

trylock(有参)方法如果获取不到锁,会等待一段时间,如果这段时间内获取到锁即正常执行,获取不到则不再竞争锁,转为继续执行代码。

  1. import com.sun.applet2.Applet2;
  2. import java.util.concurrent.TimeUnit;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class TestReentrantLock {
  5. private static ReentrantLock lock = new ReentrantLock();
  6. public static void main(String[] args) throws InterruptedException {
  7. Thread t1 = new Thread(()->{
  8. System.out.println("尝试获取锁");
  9. try {
  10. if(!lock.tryLock(2, TimeUnit.SECONDS)){
  11. System.out.println("获取不到锁");
  12. return;
  13. }
  14. } catch (InterruptedException e) {
  15. //这里如果在等待时间内被interrupt,也会退出等待状态,进入此catch块中作异常处理
  16. e.printStackTrace();
  17. System.out.println("获取不到锁");
  18. return;
  19. }
  20. try{
  21. System.out.println("获得到锁");
  22. }finally {
  23. lock.unlock();
  24. }
  25. },"t1");
  26. lock.lock();//主线程先一步获得锁
  27. System.out.println("获得到锁");
  28. t1.start();
  29. Thread.sleep(1000);
  30. System.out.println("释放了锁");
  31. lock.unlock();//手动释放锁
  32. }
  33. }

公平锁:

公平锁是指,当锁被释放时,竞争锁的线程们不会按照顺序获取锁,而是一拥而上地竞争锁,synchronized是不公平锁,ReentrantLock默认是一种不公平锁,在构造方法中可以传入True设置为公平锁。
一般公平锁的使用情况较少,因为会降低线程的并发性。