死锁和活锁
死锁
多个线程相互占用对方的资源的锁,而又相互等对方释放锁,导致阻塞等待。
例如如下代码就会导致死锁:
public class Test {
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (a) {
try {
System.out.println("threadA lock a");
TimeUnit.SECONDS.sleep(10);
synchronized (b) {
System.out.println("threadA lock b");
}
} catch (Exception e) {
}
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (b) {
try {
System.out.println("threadB lock b");
TimeUnit.SECONDS.sleep(10);
synchronized (a) {
System.out.println("threadB lock a");
}
} catch (Exception e) {
}
}
}
});
threadA.start();
threadB.start();
}
}
死锁检测:
首先,通过jps确定当前执行任务的进程号
$ jps
40040 Jps
18441 Bootstrap
39693 Launcher
39694 Test
确定任务进程号是39694,然后执行jstack命令查看当前进程堆栈信息:
$ jstack -F 39694
Attaching to process ID 39694, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
Deadlock Detection:
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock Monitor@0x00007fac2101cea8 (Object@0x00000007956e7850, a java/lang/Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock Monitor@0x00007fac2101a4b8 (Object@0x00000007956e7840, a java/lang/Object),
which is held by "Thread-0"
Found a total of 1 deadlock.
可以看到,进程的确存在死锁,两个线程分别在等待对方持有的Object对象。
也可以通过jconsole来查看死锁:
通过以上截图可以看到,Thread-0 线程出于 blocked 状态。
死锁预防:
- 按照一定的顺序对资源加锁
- 一次性获得所有的锁
- 超时放弃
那么死锁的时候,CPU占用怎么样?取决于死锁的实现:
-
自旋锁:拿不到锁的时候,忙等待,反复探测锁状态,直到拿到锁,进入临界区。这种情况会消耗CPU。
-
休眠锁:拿不到锁的时候,放弃CPU,休眠,离开运行队列,这种情况不会消耗CPU。java的synchronized应该是休眠锁。
出于死锁状态的线程的状态是 BLOCKED ,所以是不占用CPU资源的?
线程池死锁。。。
final ExecutorService executorService =
Executors.newSingleThreadExecutor();
Future<Long> f1 = executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("start f1");
Thread.sleep(1000);//延时
Future<Long> f2 =
executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("start f2");
return -1L;
}
});
System.out.println("result" + f2.get());
System.out.println("end f1");
return -1L;
}
});
jstack结果:
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode):
"Attach Listener" #11 daemon prio=9 os_prio=31 tid=0x00007ff85180c000 nid=0x1f07 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"DestroyJavaVM" #10 prio=5 os_prio=31 tid=0x00007ff851876800 nid=0x1003 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"pool-1-thread-1" #9 prio=5 os_prio=31 tid=0x00007ff852833800 nid=0x4c03 waiting on condition [0x00007000010c6000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000079577a010> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at Test$1.call(Test.java:28)
at Test$1.call(Test.java:15)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
这个例子中,线程池的任务1依赖任务2的执行结果,但是线程池是单线程的,也就是说任务1不执行完,任务2永远得不到执行,那么因此造成了死锁。
可以从jstack结果看到 当前线程wait在java.util.concurrent.FutureTask对象上。
活锁
获取部分资源,但是无法获取全部,所以释放资源,并重新获取,如此反复。
以哲学家问题为例:
如果所有的哲学家都先拿左手边的筷子后拿右手边的筷子,哲学家们就会死锁,因为他们右手边的筷子被另一个哲学家当作左手边的筷子拿走了。哲学家们发现如果他们“持有并等待”就会发生死锁,于是他们达成协议:如果申请第二根筷子没有成功,则放下第一根筷子,重新拿筷子,于是他们就形成了一个活锁。
示例代码:
class Philosopher implements Runnable {
private int id;
public Philosopher(int id) {
this.id = id;
}
@Override
public void run() {
int leftCsIndex = id;
int rightCsIndex = (id + 1) % 5;
while (true) {
LiveLockTest.chopsticks[leftCsIndex].lock();
System.out.println("Philosopher" + id + ": I got left chopstick");
try {
Thread.sleep(100);
} catch (Exception e) {
}
if (LiveLockTest.chopsticks[rightCsIndex].tryLock()) {
System.out.println("Philosopher" + id + ": I got right chopstick");
System.out.println("Philosopher" + id + ": eating");
LiveLockTest.chopsticks[rightCsIndex].unlock();
break;
} else {
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
LiveLockTest.chopsticks[leftCsIndex].unlock();
}
}
}
public class LiveLockTest {
public static Lock[] chopsticks = new Lock[5];
public static void main(String[] args) {
for (int i = 0; i < chopsticks.length; i++) {
chopsticks[i] = new ReentrantLock();
}
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new Philosopher(i));
}
exec.shutdown();
}
}
饥饿
多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。
分布式锁
分布式环境中,可能是不同语言对应的多个不同的进程,需要对某一个共享资源进行访问,会有不一致的情况,比如数据库的某张表。
参考
- Java面试必问-死锁终极篇 - https://juejin.im/post/5aaf6ee76fb9a028d3753534
- Java死锁排查和Java CPU 100% 排查的步骤整理和OOM FullGc案例 - https://blog.csdn.net/u010648555/article/details/80721815
- 线程死锁是不是cpu资源一定会居高不下?
- 并发进阶(十)饥饿与活锁 - https://zhuanlan.zhihu.com/p/40612679
- 百度百科-活锁
- java多线程中的死锁、活锁、饥饿、无锁 - https://blog.csdn.net/huangshulang1234/article/details/79158306