理解 Jdk Lock 的使用

JDK 1.5 新增了 ReentrantLock 类,可以实现线程之间的同步互斥,相比 synchronized ,在扩展功能上更加强大,在使用上更加灵活。

ReentrantLock 还具有嗅探锁定、多路分支通知等功能。

Lock

ReentrantLock

Lock 使用通过 Lock 对象调用 lock() 加锁,调用 unlock() 解锁,通常会在 try…finally 的语句块里调用解锁。

调用了 lock.lock() 代码的线程持有 对象监视器,其他线程只有等待锁释放时再次争抢。效果和使用 synchronized 关键字一样,线程之间执行顺序是随机的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LockService {

private Lock lock = new ReentrantLock();

public void testMethod() {
lock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + String.valueOf(i));
}
} finally {
lock.unlock();
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-2 0
Thread-2 1
Thread-2 2
Thread-2 3
Thread-2 4
Thread-3 0
Thread-3 1
Thread-3 2
Thread-3 3
Thread-3 4
Thread-4 0
Thread-4 1
Thread-4 2
Thread-4 3
Thread-4 4

从运行结果来看,当前线程打印完毕之后将锁释放,其他线程才可以继承打印。

Condition 通知

Lock 实现类 ReentrantLock 可以实现 等待 / 通知 模式,但需要借助于 Condition 对象。

Condition 类是在 JDK 5 中出现的技术,相对于 synchronized 与 wait() 和 notify() / notifyAll() 方法结合使用更加灵活,比如可以实现多路通知功能,也就是在一个 Lock 对象里可以创建多个 Condition(即对象监视器) 实例,线程对象可以注册在指定的 Condition 中,从而可以有选择地进行线程通知,在调度线程上更加灵活。

在使用 notify() / notifyAll() 方法进行通知时,被通知的线程却是由 JVM 随机选择的。但 ReentrantLock 结合 Condition 类是可以实现 选择通知 的功能,是 Condition 默认提供的。

synchronized 相当于整个 Lock 对象中只有一个单一的 Condition 对象,所有的线程都注册在它一个对象的身。线程开始 notifyAll() 时,需要通知所有的 WAITING 线程,没有选择权,会出现较大的效率问题。

调用 condition.await() 之前,必须先调用 lock.lock() 获得同步监视器,否则会报 IllegalMonitorStateException 错误 。
Condition 对象的 await() 方法是使当前执行任务的线程进入等待 WAITING 状态。

实现 等待 / 通知 模式

  • Object 类中的 wait() 方法相当于 Condition 类中的 await() 方法。
  • Object 类中的 wait(long timeout) 方法相当于 Condition 类中的 await(long time, TimeUnit) 方法。
  • Object 类中的 notify() 方法相当于 Condition 类中的 signal() 方法。
  • Object 类中的 notifyAll() 方法相当于 Condition 类中的 signalAll() 方法。

可以创建多个 Condition,每个线程的 Condition 条件互斥,可以实现多个线程顺序执行。

等待/通知示例

  1. Lock,Condition 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class LockService {

    private Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();

    public void await(){
    try {
    lock.lock();
    System.out.println("await start time = " + System.currentTimeMillis());
    condition.await();
    System.out.println("afater await");
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    System.out.println("finally await unlock...........");
    lock.unlock();
    }
    }

    public void signal(){
    try {
    lock.lock();
    System.out.println("signal start time = " + System.currentTimeMillis());
    condition.signal();
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    System.out.println("finally signal unlock...........");
    lock.unlock();
    }
    }
    }
  2. 线程类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class LockThread extends Thread {

    private LockService lockService;

    public LockThread(LockService lockService) {
    super();
    this.lockService = lockService;
    }

    @Override
    public void run(){
    lockService.await();
    }
    }
  3. 运行线程类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class ConditionRun {

    public static void main(String[] args) {
    try {
    LockService lockService = new LockService();
    LockThread lockThread = new LockThread(lockService);
    lockThread.start();
    Thread.sleep(3000);
    lockService.signal();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
  4. 执行结果

    1
    2
    3
    4
    5
    6
    await start time = 1566724151506
    Disconnected from the target VM, address: '127.0.0.1:63994', transport: 'socket'
    signal start time = 1566724154521
    finally signal unlock...........
    afater await
    finally await unlock...........

公平锁与非公平锁

锁 Lock 分为 公平锁非公平锁

公平锁

公平锁 表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的 FIFO 先进先出顺序。

通过 Lock lock = new ReentrantLock() 来创建锁,默认是 非公平锁,而要创建公平锁,需要开启 公平策略,即调用 public ReentrantLock(boolean fair) 构造,传入 true,即使用 Lock lock = new ReentrantLock(true) 来创建锁。

查看 ReentrantLock 的构造方法源码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
..........
//默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//fair=true 则开启公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
..........
}

开启公平锁的线程,打印结果基本是呈有序的状态。

非公平锁

非公平锁 是一种获取锁的抢占机制,是随机获得锁的,不是先来先得的公平锁方式,非公平锁可能造成某些线程一直拿不到锁,结果就是不公平的了。

非公平锁的运行结果基本上是乱序的,说明先 start() 启动的线程不代表先获得锁。

Lock 方法

getHoldCout()

方法 int getHoldCount() 的作用是查询当前线程保持此锁的个数,也就是调用 lock() 方法的次数。

getQueueLength()

方法 int getQueueLength() 的作用是返回正等待获取此锁定的线程估计数,比如有 5 个线程,1 个线程首先执行 await() 方法,那么调用 getQueueLength() 方法后的返回值是 4,说明有 4 个线程同时在等待 lock 的释放。

getWaitQueueLength()

方法 int getWaitQueueLength(Condition condition) 的作用是返回等待此锁定相关的给定条件 Condition 的线程估计数。

比如有 5 个线程,每个线程都执行了同一个 condition 对象的 await() 方法,则调用 getWaitQueueLength(Condition condition) 方法时返回的 int 值是 5。

相当于根据 Condition 对象条件判断等待锁的线程数。

hasQueuedThread()

方法 boolean hasQueuedThread(Thread thread) 是查询指定的线程是否正在等待获取此锁。

hasQueuedThreads()

方法 boolean hasQueuedThreads() 是查询是否有线程正在等待获取此锁。

hasWaiters()

方法 boolean hasWaiters(Condition condition) 是查询是否有线程正在等待与此锁定有关的 condition 条件。

isFair()

方法 boolean isFair() 判断是不是公平锁,是的话返回 true。

isHeldByCurrentThread()

方法 boolean isHeldByCurrentThread() 查询当前线程是否持有此锁。

isLocked()

方法 boolean isLocked() 查询些锁是否有任意线程持有。

lockInterruptibly()

方法 void lockInterruptibly() :如果当前线程未被中断,则获取锁定,如果已被中断,则出现异常。

tryLock()

方法 boolean tryLock() :只有在调用时,获取没有被另一个线程持有的锁。

tryLock(long timeout, TimeUnit unit)

方法 boolean tryLock(long timeout, TimeUnit unit):如果锁在给定的等待时间内没有被另一个线程持有并且当前线程未被中断(interrupted),则获取该锁。

Condition 方法

awaitUninterruptibly()

方法 void awaitUninterruptibly():使当前线程等待,直到被中断,不会抛出异常。

awaitUntil()

方法 boolean awaitUntil(Date deadline):使当前线程等待,直到被其它线程唤醒或被中断,或等待时间已过。即线程在等待时间可以被其它线程唤醒。

ReentrantReadWriteLock

ReentrantLock 是具有完全互斥排他的效果,即同一时间只有一个线程在执行 ReentrantLock.lock() 后面的任务。这样做保证了实例变量的线程安全性,但效率非常低下。

ReentrantReadWriteLock 是一种读写锁,在某些不需要操作实例变量的方法中,可以使用读写锁来提升 lock() 方法的运行速度。

读写锁

读写锁表示有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读操作之间不互斥,读锁与写锁互斥,写锁与写锁互斥。

在没有线程执行写操作时,多个读操作线程都可以获取 读锁,而要进行写入操作的线程只有获取写锁后才能进行写入操作。

即多个线程可以同时进行读操作,但同一时刻只允一个线程进行写入操作。

读写锁:读读共享,异步非互斥、读写互斥、写读互斥、写写互斥。

作者

光星

发布于

2019-06-24

更新于

2022-06-17

许可协议

评论