死锁有四个条件
1. 互斥性 某个锁单元只能被某个运算单元占用
2. 不可以剥夺 线程获取某个资源, 这个资源不可以被其他资源剥夺, 只能自己释放,
3. 请求并保持 请求别的资源的同时却保持占有某个资源
4. 形成循环链路
一般锁都有互斥性, 读锁没有互斥性. 一般的锁都不可以被剥夺的, 除非自己主动释放, 解决办法是当获取其他锁超过一定时间, 释放自己占有的锁资源 , 一般的死锁都是请求并保持, 一个持有A资源, 去请求B资源, 另一个线程持有B资源, 却是请求A资源. 一般解决办法是对资源进行排序, 按顺序获取资源. 形成循环链路, java1.7hashMap头插法, 就有可能形成环形链路.
java死锁的例子
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
synchronized (A) {
Thread.sleep(2000);
System.out.println("线程甲获取A锁");
synchronized (B) {
System.out.println("线程甲获取B锁");
}
}
}
}).start();
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
synchronized (B) {
Thread.sleep(2000);
System.out.println("线程乙获取B锁");
synchronized (A) {
System.out.println("线程乙获取A锁");
}
}
}
}).start();
}
使用visualVM查看线程情况, 发现线程死锁提醒
查看线程dump, 发现线程Thread-0和Thread-1有BLOCK字段, 表示线程被阻塞.
- waiting to lock <0x0000000702024290> (a java.lang.Object)
- locked <0x00000007020242a0> (a java.lang.Object)
线程1等待锁资源<0x0000000702024290>, 自己占用锁资源<0x00000007020242a0>
- waiting to lock <0x00000007020242a0> (a java.lang.Object)
- locked <0x0000000702024290> (a java.lang.Object)
线程0等待锁资源<0x00000007020242a0>, 自己占用锁资源<0x0000000702024290>
所以综上所述, 这两个线程互相争夺对方的资源而形成死锁.
使用jconsole, 线程窗口, 点击检测死锁.
Thread-0死锁锁定Object, 显示object的地址
C:\software\jdk1.8.0_101\bin>jps -l
20704 com.jesse.log.LockTest
26576 org/netbeans/Main
5156 org.netbeans.lib.profiler.server.ProfilerServer
1304 org.jetbrains.jps.cmdline.Launcher
24232 sun.tools.jps.Jps
26312
25516 sun.tools.jconsole.JConsole
使用jps -l 显示java 线程的pid和类路径
C:\software\jdk1.8.0_101\bin>jstack -l 20704
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000001a3033d8 (object 0x000000070201c1c0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000001a300d58 (object 0x000000070201c1d0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.jesse.log.LockTest$2.run(LockTest.java:37)
- waiting to lock <0x000000070201c1c0> (a java.lang.Object)
- locked <0x000000070201c1d0> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at com.jesse.log.LockTest$1.run(LockTest.java:24)
- waiting to lock <0x000000070201c1d0> (a java.lang.Object)
- locked <0x000000070201c1c0> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
Thread-0等待锁监视器 0x000000001a300d58 (object 0x000000070201c1d0, a java.lang.Object),
但是Thread-0却持有锁<0x000000070201c1c0>
Thread-1等待锁监视器0x000000001a3033d8 (object 0x000000070201c1c0, a java.lang.Object),
但是Thread-1却持有锁<0x000000070201c1d0>
所以就形成死锁.
使用jmc, 启动jmx, 点击线程面板, 选中死锁检测
java中有悲观锁和乐观锁
悲观锁
有synchronized修饰的方法和代码块
还有基于AQS的锁
ReentrantLock 支持公平非公平, 支持可重入, 支持中断
ReentrantReadWriteLock 可以生成读写锁, 读读不互斥, 某段代码可以多个线程同时执行的, 可以用读锁.
Semaphore 信号量, 可以用做限流
countDownLatch 可以用做等待一堆任务完成才开始其他任务.
java的乐观锁是通过CAS实现的.
CAS主要过程是先获取某个地址的值, 在修改的时候, 会去比较和之前获取的值是否相等, 如果相等就修改成新值, 如果不相等, 不做处理. 这种情况下会遇到ABA问题, 就是线程1获取某个变量的值为A, 刚好线程2修改了这个变量为B, 然后有很无聊把它改回A. 这时候切换到线程1, 发现变量的和之前的值相等, 所以做了更新操作. 解决这个问题的方法时加一个版本. 每修改一次, 版本都变一次, 最后线程1虽然获得变量和之前获取的一样, 但是版本不一致, 所以不做处理