Skip to Content

Java锁的活跃性

多把锁

一间大屋子有两个功能:睡觉、学习,互不相干。

现在A要学习,B要睡觉,但如果只用一间屋子(一个对象锁)的话,并发度很低,解决方法是准备多个房间(多个对象锁)。

class BigRoom { private final Object studyRoom = new Object(); private final Object bedRoom = new Object(); public void sleep() { // synchronized (this) { synchronized (bedRoom) { log.debug("sleeping 2 小时"); Sleeper.sleep(2); } } public void study() { // synchronized (this) { synchronized (studyRoom) { log.debug("study 1 小时"); Sleeper.sleep(1); } } }

将锁的粒度细分

  • 好处,是可以增强并发度
  • 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。

避免死锁要注意加锁顺序,另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查。

t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁。

例:

Object A = new Object(); Object B = new Object(); Thread t1 = new Thread(() -> { synchronized (A) { log.debug("lock A"); sleep(1); synchronized (B) { log.debug("lock B"); log.debug("操作..."); } } }, "t1"); Thread t2 = new Thread(() -> { synchronized (B) { log.debug("lock B"); sleep(0.5); synchronized (A) { log.debug("lock A"); log.debug("操作..."); } } }, "t2"); t1.start(); t2.start();

定位死锁

检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:

jconsole工具:

JPS定位死锁:

下图可以看到,t1线程等待的锁,被t2线程锁着,所以造成死锁。

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如:

static volatile int count = 10; static final Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { // 期望减到 0 退出循环 while (count > 0) { sleep(0.2); count--; log.debug("count: {}", count); } }, "t1").start(); new Thread(() -> { // 期望超过 20 退出循环 while (count < 20) { sleep(0.2); count++; log.debug("count: {}", count); } }, "t2").start(); }

可以让两个线程的执行交错开,基本可以解决活锁的问题。

饥饿

大部分的定义:一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束

不按顺序加锁,容易发生死锁:

顺序加锁,容易发生饥饿:

Last updated on