乐趣区

Java多线程中的显式锁

1. 接口方法展示

//Lock 接口: java.util.concurrent.locks.Lock
public interface Lock {void lock();
    
    void lockInterruptibly() throws InterruptedException;
    
    boolean tryLock();
    
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    
    void unlock();
    
    Condition newCondition();}

2. 方法分析

以上 6 个方法是 Lock 接口所需要实现的方法。Lock 中最常用的实现类是ReentrantLock。这里只针对与加锁操作相关的方法进行分析和区别。

  • void lock(): 当锁空闲时,当前线程立即获取锁;当锁被其他线程占用时,当前线程进入阻塞等待调度并最终获取锁。此方法在功能上与 synchronized 关键字最接近。可阻塞但不能响应线程中断。
  • void lockInterruptibly() throws InterruptedException: 当锁空闲时,当前线程立即获取锁;当锁被其他线程占用时,当前线程进入阻塞等待调度并最终获取锁。可阻塞,可响应线程中断,抛出 InterruptedException 异常。
  • boolean tryLock(): 只尝试一次取锁操作,如果锁空闲,获得锁并立即返回true,不管此时是否已经有其他线程在队列中等待获取该锁,相当于“插队”,并且这种插队行为直接无视锁是否具有公平性;如果锁被其他线程持有,立即返回false。不可阻塞,同时不可响应中断。
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException: 当锁空闲时,当前线程立即获得锁并返回 true;当锁被其他线程持有时,进入阻塞并在 timeout 到来之前等待调度获得该锁。如果最终能够获得锁,则返回true,否则返回false。可阻塞(最大阻塞时长由 timeout 界定),同时可响应线程中断。在其实现类ReentrantLock 中,当使用公平锁时,该方法行为有所不同。如果当前锁处于空闲状态,但已经有其他线程在队列中等待该获取该锁,那么当前线程不会“插队”,而是进入队列等待,确保公平性。

3. 显式锁与内置锁的性能对比

在 Java 5 以及之前的版本中,显式锁的并发性能明显好于对象的内置锁。但从 Java 6 开始,随着 JVM 在处理 syncronized 关键字的不断优化,二者的并发性能已经很接近了。因此,除非内置锁不能满足并发程序的设计需求,通常情况下还是建议使用 synchonized 内置锁。

4. 谈谈锁的公平性问题

公平锁与非公平锁的区别在于:在非公平锁中,只有当锁被某个线程持有时,当前线程才会被放入到锁的等待队列中。换句话说,如果使用的是非公平锁,如果一个线程看到这个锁当前处于可用状态,则无论是否有其他线程已经在队列中等待这个锁,当前的线程都会耍流氓插队。

除非锁的公平性会影响程序执行的正确性,一般情况下,推荐使用非公平锁,因为非公平锁的并发性能更好。同时,Java 也没有要求 JVM 以公平的方式实现内置锁。

退出移动版