重新理解线程状态转换
假设有线程 Thread t
情况 1 NEW --> RUNNABLE
当调用 t.start() 方法时,由 NEW --> RUNNABLE
情况 2 RUNNABLE <–> WAITING
t 线程用 synchronized(obj) 获取了对象锁后
- 调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING
- 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
当一个线程被interrupt()方法中断时,它会从WAITING状态转换为RUNNABLE状态,并抛出一个InterruptedException异常。在此之后,它就可以继续执行了,但需要注意的是,在处理InterruptedException异常之前,它必须先清除中断状态,否则可能会导致线程再次进入WAITING状态。
- 竞争锁成功,t 线程从 WAITING --> RUNNABLE
- 竞争锁失败,t 线程从 WAITING --> BLOCKED(当线程没办法获得锁的时候,就会进入 block状态)
final static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { new Thread(() -> { synchronized (obj) { System.out.println("执行t1...."); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("其它代码...."); } },"t1").start(); new Thread(() -> { synchronized (obj) { System.out.println("执行t2...."); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("其它代码...."); } },"t2").start(); Thread.sleep(500); System.out.println("唤醒 obj 上其它线程"); synchronized (obj) { obj.notifyAll(); // 唤醒obj上所有等待线程 断点 } }
可以看到,当发生竞争的时候t1的状态是 Monitor
情况 3 RUNNABLE <–> WAITING
当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING,注意是当前线程在t 线程对象的监视器上等待
t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE
情况 4 RUNNABLE <–> WAITING
- 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING
- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE
情况 5 RUNNABLE <–> TIMED_WAITING
t 线程用 synchronized(obj) 获取了对象锁后
- 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING
- t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE
竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED
情况 6 RUNNABLE <–> TIMED_WAITING
当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING,注意是当前线程在t 线程对象的监视器上等待
当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从
TIMED_WAITING --> RUNNABLE
情况 7 RUNNABLE <–> TIMED_WAITING
当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE
情况 8 RUNNABLE <–> TIMED_WAITING
当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线
程从 RUNNABLE --> TIMED_WAITING
调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从
TIMED_WAITING–> RUNNABLE
情况 9 RUNNABLE <–> BLOCKED
t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED
持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED
情况 10 RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
多把锁
多把不相干的锁
一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁)
class BigRoom { public void sleep() { synchronized (this) { log.debug("sleeping 2 小时"); Sleeper.sleep(2); } } public void study() { synchronized (this) { log.debug("study 1 小时"); Sleeper.sleep(1); } } }
执行
BigRoom bigRoom = new BigRoom(); new Thread(() -> { bigRoom.study(); },"小南").start(); new Thread(() -> { bigRoom.sleep(); },"小女").start();
某次结果
12:13:54.471 [小南] c.BigRoom - study 1 小时 12:13:55.476 [小女] c.BigRoom - sleeping 2 小时
时间上可以看出,小女晚了1s执行
改进
class BigRoom { private final Object studyRoom = new Object(); private final Object bedRoom = new Object(); public void sleep() { synchronized (bedRoom) { log.debug("sleeping 2 小时"); Sleeper.sleep(2); } } public void study() { synchronized (studyRoom) { log.debug("study 1 小时"); Sleeper.sleep(1); } } }
某次执行结果
12:15:35.069 [小南] c.BigRoom - study 1 小时 12:15:35.069 [小女] c.BigRoom - sleeping 2 小时
时间上可以看出,是同步执行的。
将锁的粒度细分
- 好处,是可以增强并发度(锁的细粒度能够提升并发度)
- 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
活跃性
线程内的代码本来是有限的,但是由于某种原因,你的线程里面的代码一直执行不完,这就叫做线程的活跃性。
死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
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();
12:22:06.962 [t2] c.TestDeadLock - lock B 12:22:06.962 [t1] c.TestDeadLock - lock A
定位死锁
检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:
jps
cmd > jps Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 12320 Jps 22816 KotlinCompileDaemon 33200 TestDeadLock // JVM 进程 11508 Main 28468 Launcher
cmd > jstack 33200 Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 2018-12-29 05:51:40 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b14 mixed mode): "DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003525000 nid=0x2f60 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Thread-1" #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting for monitor entry [0x000000001f54f000] java.lang.Thread.State: BLOCKED (on object monitor) at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28) - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object) - locked <0x000000076b5bf1d0> (a java.lang.Object) at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-0" #11 prio=5 os_prio=0 tid=0x000000001eb68800 nid=0x1b28 waiting for monitor entry [0x000000001f44f000] java.lang.Thread.State: BLOCKED (on object monitor) at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15) - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object) - locked <0x000000076b5bf1c0> (a java.lang.Object) at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) // 略去部分输出 Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28) - waiting to lock <0x000000076b5bf1c0> (a java.lang.Object) - locked <0x000000076b5bf1d0> (a java.lang.Object) at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-0": at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15) - waiting to lock <0x000000076b5bf1d0> (a java.lang.Object) - locked <0x000000076b5bf1c0> (a java.lang.Object) at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) Found 1 deadlock
这里是两个线程 “Thread-0” 和 “Thread-1” 在互相等待对方释放锁,从而导致了死锁。
线程 “Thread-1” 试图获取对象0x000000076b5bf1c0的监视器锁(即synchronized关键字保护的那个对象),但是这个锁已经被线程 “Thread-0” 持有。因此,“Thread-1” 进入了等待状态,等待 “Thread-0” 释放这个锁。
同时,线程 “Thread-0” 试图获取对象0x000000076b5bf1d0的监视器锁,但是这个锁已经被线程 “Thread-1” 持有。因此,“Thread-0” 进入了等待状态,等待 “Thread-1” 释放这个锁。
剑指JUC原理-7.线程状态与ReentrantLock(中):https://developer.aliyun.com/article/1413619