在JDK5中增加了Lock锁接口,有ReentrantLock实现类,ReentrantLock锁称为可重入锁,它功能比synchronized多。

5.1 锁的可重入性

当一个线程获得一个对象锁后,再次请求该对象锁时可以获得该对象的锁。

5.2 ReentrantLock

5.2.1 基本使用

  1. public class Test01 {
  2. /**
  3. * 定义显示锁
  4. */
  5. static Lock lock = new ReentrantLock();
  6. /**
  7. * 定义方法
  8. */
  9. public static void sm(){
  10. //先获得锁
  11. lock.lock();
  12. try{
  13. int num = 100;
  14. for (int i = 0; i < num; i++){
  15. System.out.println(Thread.currentThread().getName() + " -- " + i);
  16. }
  17. } finally {
  18. // 释放锁
  19. lock.unlock();
  20. }
  21. }
  22. public static void main(String[] args) {
  23. Runnable runnable = () -> sm();
  24. // 启动三个线程
  25. new Thread(runnable).start();
  26. new Thread(runnable).start();
  27. new Thread(runnable).start();
  28. }
  29. }

5.2.2 lockInterruptibly()

如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常。

  1. public class Test02 {
  2. static class Service{
  3. private Lock lock = new ReentrantLock();
  4. public void serviceMethod() {
  5. // 被锁定后,即使调用线程的interrupt()方法也没有真正中断
  6. // lock.lock();
  7. try {
  8. lock.lockInterruptibly();
  9. try {
  10. System.out.println(Thread.currentThread().getName() + " -- begin lock");
  11. for (int i = 0; i < Integer.MAX_VALUE; i++){
  12. new StringBuilder();
  13. }
  14. System.out.println(Thread.currentThread().getName() + " -- end lock");
  15. }finally {
  16. lock.unlock(); // if lock.isHeldByCurrentThread
  17. System.out.println(Thread.currentThread().getName() + " ** 释放锁");
  18. }
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. }
  24. public static void main(String[] args) throws InterruptedException {
  25. Service s = new Service();
  26. Runnable r = s::serviceMethod;
  27. Thread t1 = new Thread(r);
  28. t1.start();
  29. Thread.sleep(50);
  30. Thread t2 = new Thread(r);
  31. t2.start();
  32. Thread.sleep(50);
  33. t2.interrupt();
  34. }
  35. }

对于synchronized内部锁来说,如果一个线程在等待锁,只有两个结果,要么该线程获得锁继续执行,要么就保持等待。
对于ReentrantLock可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求。

5.2.3 tryLock()方法

tryLock(long time, TimeUnit unit)的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁,通过该方法可以实现锁对象的限时等待。
tryLock()仅在调用时锁定未被其他线程持有的锁。

5.2.4 newCondition()方法

关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式。Lock锁的newCondition()方法返回Condition对象,Condition类也可以实现等待/通知模式。
使用nofify()通知时,JVM会随机唤醒某个等待的线程,使用Condition类可以进行选择性通知。Condition比较常用的两个方法:

  1. await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
  2. signal()用于唤醒一个等待的线程。

    注意:在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在singal()调用后会从当前Condition对象的等待队列中唤醒一个线程,唤醒的线程尝试获得锁,一旦获得锁成功就继续执行。

  1. public class Test01 {
  2. static Lock lock = new ReentrantLock();
  3. static Condition condition = lock.newCondition();
  4. static class SubThread extends Thread {
  5. @Override
  6. public void run() {
  7. lock.lock();
  8. try {
  9. System.out.println("method lock");
  10. condition.await();
  11. System.out.println("method await");
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. } finally {
  15. lock.unlock();
  16. System.out.println("method unlock");
  17. }
  18. }
  19. }
  20. public static void main(String[] args) {
  21. SubThread t = new SubThread();
  22. t.start();
  23. try {
  24. Thread.sleep(3000);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. // 主线程想要唤醒子线程,需要先持有锁,否则会出现异常
  29. lock.lock();
  30. try {
  31. condition.signal();
  32. } finally {
  33. lock.unlock();
  34. }
  35. }
  36. }

5.2.5 公平锁与非公平锁

大多数情况下,锁的申请都是非公平的,如果线程1与线程2都在请求锁A,当锁A可用时,系统只是会从阻塞队列中随机地选择一个线程,不能保证其公平性。
公平的锁会按照时间先后顺序,保证先到先得,公平锁这一特点不会出现线程饥饿现象。
synchronized内部锁就是非公平的,ReentrantLock重入锁提供了一个构造方法:ReentrantLock(boolean fair),当在创建锁对象时,实参传递true可以把该锁设置为公平锁。

线程接连获得/释放锁, 如果是非公平锁,系统倾向于让一个线程再次获得已经持有的锁,这种分配策略是高效的、非公平的; 如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性。

要实现公平锁,必须要求系统维护一个有序队列,公平锁的实现成本较高,性能低,因此默认情况下锁是非公平的。

5.2.6 其他常用方法

  1. int getHoldCount():返回当前线程调用lock()方法的次数;
  2. int getQueueLenghth():返回等待获得锁的线程预估数;
  3. int getWaitQueueLength(Condition condition):返回与Condition条件相关的等待线程预估数;
  4. boolean hasQueuedThread(Thread thread):查询参数指定的线程是否在等待获得锁;
  5. boolean hasQueuedThread():查询是否还有线程在等待获得该锁;
  6. boolean hasWaiters(Condition condition):查询是否有线程正在等待指定的Condition条件;
  7. boolean isFair():判断是否为公平锁;
  8. boolean isHeldByCurrentThread():判断当前线程是否持有该锁;
  9. boolean isLocked():查询当前锁是否被线程持有。

    5.3 ReentrantReadWriteLock读写锁

    synchronized内部锁与ReentrantLock锁都是独占锁(排他锁),同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。
    ReentrantReadWriteLock读写锁是一种改进的排他锁,也可以称为共享/排他锁,允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新。
    读写锁通过读锁与写锁来完成读写操作,线程在读取共享数据前,必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的;线程在修改共享数据前必须持有写锁,写锁是排他的。一个线程持有写锁时,其他线程无法获得相应的锁。
    在java.util.concurrent.locks包中定义了ReadWriteLock接口,该接口中定义了readLock()返回读锁,定义writeLock()方法返回写锁,该接口的实现类是ReentrantReadWriteLock。

    readLock()与writeLock()方法返回的锁对象是同一个锁的两个不同的角色,不是分别获得两个不同的锁。

5.3.1 读读共享

  1. public class Test01 {
  2. static class Service{
  3. ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  4. /**
  5. * 输出:
  6. * Thread-0获得读锁,开始读取数据的时间--1614927014969
  7. * Thread-3获得读锁,开始读取数据的时间--1614927014970
  8. * Thread-4获得读锁,开始读取数据的时间--1614927014970
  9. * Thread-2获得读锁,开始读取数据的时间--1614927014970
  10. * Thread-1获得读锁,开始读取数据的时间--1614927014970
  11. */
  12. public void read(){
  13. readWriteLock.readLock().lock();
  14. try {
  15. System.out.println(Thread.currentThread().getName()
  16. + "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
  17. TimeUnit.SECONDS.sleep(2);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. } finally {
  21. readWriteLock.readLock().unlock();
  22. }
  23. }
  24. }
  25. public static void main(String[] args) {
  26. Service service = new Service();
  27. int threadNum = 5;
  28. for (int i = 0; i < threadNum; i++){
  29. new Thread(service::read).start();
  30. }
  31. }
  32. }

5.3.2 写写互斥

  1. public class Test02 {
  2. static class Service{
  3. ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  4. /**
  5. * Thread-0获得写锁,开始修改数据的时间--1614927264280
  6. * Thread-0修改数据结束的时间--1614927266283
  7. * Thread-2获得写锁,开始修改数据的时间--1614927266283
  8. * Thread-2修改数据结束的时间--1614927268284
  9. * Thread-1获得写锁,开始修改数据的时间--1614927268284
  10. * Thread-1修改数据结束的时间--1614927270284
  11. * Thread-4获得写锁,开始修改数据的时间--1614927270284
  12. * Thread-4修改数据结束的时间--1614927272285
  13. * Thread-3获得写锁,开始修改数据的时间--1614927272285
  14. * Thread-3修改数据结束的时间--1614927274285
  15. */
  16. public void write(){
  17. readWriteLock.writeLock().lock();
  18. try {
  19. System.out.println(Thread.currentThread().getName()
  20. + "获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
  21. TimeUnit.SECONDS.sleep(2);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. } finally {
  25. System.out.println(Thread.currentThread().getName()
  26. + "修改数据结束的时间--" + System.currentTimeMillis());
  27. readWriteLock.writeLock().unlock();
  28. }
  29. }
  30. }
  31. public static void main(String[] args) {
  32. Service service = new Service();
  33. int threadNum = 5;
  34. for (int i = 0; i < threadNum; i++){
  35. new Thread(service::write).start();
  36. }
  37. }
  38. }

5.3.3 读写互斥

  1. public class Test03 {
  2. static class Service{
  3. ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  4. public void write(){
  5. readWriteLock.writeLock().lock();
  6. try {
  7. System.out.println(Thread.currentThread().getName()
  8. + "获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
  9. TimeUnit.SECONDS.sleep(2);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. } finally {
  13. System.out.println(Thread.currentThread().getName()
  14. + "修改数据结束的时间--" + System.currentTimeMillis());
  15. readWriteLock.writeLock().unlock();
  16. }
  17. }
  18. public void read(){
  19. readWriteLock.readLock().lock();
  20. try {
  21. System.out.println(Thread.currentThread().getName()
  22. + "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
  23. TimeUnit.SECONDS.sleep(2);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. } finally {
  27. System.out.println(Thread.currentThread().getName()
  28. + "读取数据结果的时间--" + System.currentTimeMillis());
  29. readWriteLock.readLock().unlock();
  30. }
  31. }
  32. }
  33. /**
  34. * Thread-0获得读锁,开始读取数据的时间--1614927579907
  35. * Thread-0读取数据结果的时间--1614927581910
  36. * Thread-1获得写锁,开始修改数据的时间--1614927581910
  37. * Thread-1修改数据结束的时间--1614927583910
  38. */
  39. public static void main(String[] args) {
  40. Service service = new Service();
  41. new Thread(service::read).start();
  42. new Thread(service::write).start();
  43. }
  44. }