什么是死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的 synchronized 代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
死锁的产生是必须要满足一些特定条件的:
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
一个死锁的例子:
public class NormalDeadLock {
private static Object valueFirst = new Object();
private static Object valueSecond = new Object();
private static void firstToSecond() throws InterruptedException{
String threadName = Thread.currentThread().getName();
synchronized (valueFirst){
System.out.println(threadName + " get first");
Thread.sleep(100);
synchronized (valueSecond){
System.out.println(threadName + " get second");
}
}
}
private static void secondToFirst() throws InterruptedException{
String threadName = Thread.currentThread().getName();
synchronized (valueSecond){
System.out.println(threadName + " get second");
Thread.sleep(100);
synchronized (valueFirst){
System.out.println(threadName + " get first");
}
}
}
private static class TestThread extends Thread{
private String name;
public TestThread(String name){
this.name = name;
}
public void run(){
Thread.currentThread().setName(name);
try{
secondToFirst();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try{
firstToSecond();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
这个程序运行结果:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57881:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/lib/tools.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/out/production/javastudy:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-stdlib.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-reflect.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-test.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-stdlib-jdk7.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/Kotlin/kotlinc/lib/kotlin-stdlib-jdk8.jar:/Users/theodore/.m2/repository/org/thymeleaf/thymeleaf/3.0.11.RELEASE/thymeleaf-3.0.11.RELEASE.jar:/Users/theodore/.m2/repository/ognl/ognl/3.1.12/ognl-3.1.12.jar:/Users/theodore/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/Users/theodore/.m2/repository/org/attoparser/attoparser/2.0.5.RELEASE/attoparser-2.0.5.RELEASE.jar:/Users/theodore/.m2/repository/org/unbescape/unbescape/1.1.6.RELEASE/unbescape-1.1.6.RELEASE.jar:/Users/theodore/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxb-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/gmbal-api-only.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/javax.annotation.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/ha-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxws-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jsr181-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/FastInoset.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/mimepull.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/mail.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxws-rt.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxb-impl.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxb-xjc.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/policy.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/management-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/jaxws-tools.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/saaj-impl.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/stax-ex.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/stax2-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/streambuffer.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/saaj-api.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/woodstox-core-asl.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/javax.persistence.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-aspects-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-instrument-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-expression-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-context-support-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-aop-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-instrument-tomcat-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-context-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-core-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-jms-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-jdbc-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-oxm-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-messaging-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-orm-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-tx-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/commons-logging-1.2.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-test-4.3.18.RELEASE.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/aopalliance-1.0.jar:/Users/theodore/Downloads/ideaproject/xiangxue/javastudy/lib/spring-beans-4.3.18.RELEASE.jar" com.enjoy.MultiThread.ch7.NormalDeadLock
SubTestThread get second
TestDeadLock get first
程序是无法停止的,因为 SubTestThread 先获取了第二个锁,然后去尝试获取第一个锁。这个时候 TestDeadLock 先获取了第一个锁,不释放锁。SubTestThread 线程就一直等着。然后 TestDeadLock 在去获取第二个锁,这个时候第二个锁被第一个线程占用不释放。
synchronized 不走出代码块是不会释放锁的。这就导致了死锁。代码没有保证获得锁的顺序导致了两个线程互相等待。造成死锁。
再来看下面这个代码:
//不安全的转账动作
public class TransferAccount implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amout) throws InterruptedException {
synchronized (from){//先锁转出
System.out.println(Thread.currentThread().getName() + "get" + from.getName());
Thread.sleep(100);
synchronized (to){//后锁转入
System.out.println(Thread.currentThread().getName() + "get" + to.getName());
from.flyMoney(amout);
to.addMoney(amout);
}
}
}
}
这段代码也会产生死锁,为什么呢?明明代码保证了获取锁的顺序呀?
对,但是获取锁的顺序是调用的时候决定的,如果两个线程,一个是 A 转账给 B,另一个是 B 转账给 A。这个时候获取锁的顺序就又不能保证了。就造成了思索。
这两段代码第一个就是,静态死锁,第二个就是,动态死锁。那么怎么解决呢?有几个解决方案:
先定义一个账户类 有账户名字和账户余额,还实现了转入转出方法。
//用户账户实体类
public class UserAccount {
//账户名字和余额
private final String name;
private int money;
private final Lock lock = new ReentrantLock();
public Lock getLock() {
return lock;
}
public UserAccount(String name, int money) {
this.name = name;
this.money = money;
}
public String getName() {
return name;
}
public int getMoney() {
return money;
}
@Override
public String toString() {
return "UserAccount{" +
"name='" + name + '\'' +
", money=" + money +
", lock=" + lock +
'}';
}
//转入金额
public void addMoney(int amount){
money += amount;
}
//转出金额
public void flyMoney(int amount){
money -= amount;
}
}
定义一个接口:
//银行转账接口
public interface ITransfer {
void transfer(UserAccount from,UserAccount to,int amout) throws InterruptedException;
}
方案1:
//不会产生死锁的安全转账 通过hashcode保证顺序
public class SafeOperate implements ITransfer {
private static Object tieLock = new Object();//加时赛锁
@Override
public void transfer(UserAccount from, UserAccount to, int amout) throws InterruptedException {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if(fromHash<toHash){
synchronized (from){
System.out.println(Thread.currentThread().getName() + "get" + from.getName());
Thread.sleep(100);
synchronized (to){
System.out.println(Thread.currentThread().getName() + "get" + to.getName());
from.flyMoney(amout);
to.addMoney(amout);
}
}
}else if(toHash<fromHash){
synchronized (to){
System.out.println(Thread.currentThread().getName() + "get" + to.getName());
Thread.sleep(100);
synchronized (from){
System.out.println(Thread.currentThread().getName() + "get" + from.getName());
from.flyMoney(amout);
to.addMoney(amout);
}
}
}else{//哈希冲突
synchronized (tieLock){
synchronized (from){
synchronized (to){
from.flyMoney(amout);
to.addMoney(amout);
}
}
}
}
}
}
通过两个锁的 hashcode 保证一定先锁小的那个,但是 hash 会有千万分之一的冲突,虽然不多,但是也需要考虑进去。再定义一个锁,在获得 2 个对象的锁之前,就要获得这个锁。保证顺序。
方案2:
//用 trylock 保证不会产生锁
public class SafeOperateToo implements ITransfer {
@Override
public void transfer(UserAccount from, UserAccount to, int amout) throws InterruptedException {
Random r = new Random();
while (true){
if (from.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName() + " get " + from.getName());
if (to.getLock().tryLock()){
try{
System.out.println(Thread.currentThread().getName() + " get " + to.getName());
from.flyMoney(amout);
to.addMoney(amout);
break;
}finally {
to.getLock().unlock();
}
}
}finally {
from.getLock().unlock();
}
}
SleepTools.ms(r.nextInt(10));//为了保证两个锁之间不会互相谦让,导致谁都没有获得锁
}
}
}
上面那个代码看起来有些麻烦,这个方案,用 trylock 去尝试获取锁,获取不到线程也不会阻塞,在 while 循环中会再次去尝试获取锁,直到获取到了为止。但是这个有个问题,如果两个线程同时去尝试获取锁,有可能两个线程都获取不到,然后他们就一直重复这个动作。虽然没有造成死锁,但是他们都没有继续下面的任务,这个就是活锁。为了解决这个问题,我们可以对重试机制引入一些随机性,不指定同时重发,而是重发的时间内是随机的。通过随机的等待再发送能够相当有效的避免活锁的发生。
在定义一个类来测试我们的程序:
//模拟公司转账
public class PayCompay {
private static class TransferThread extends Thread{
private String name;
private UserAccount from;
private UserAccount to;
private int amount;
private ITransfer transfer;
public TransferThread(String name, UserAccount from, UserAccount to, int amount, ITransfer transfer) {
this.name = name;
this.from = from;
this.to = to;
this.amount = amount;
this.transfer = transfer;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try{
transfer.transfer(from,to,amount);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args){
PayCompay payCompay = new PayCompay();
UserAccount zhangsan = new UserAccount("zhangsan",20000);
UserAccount lisi = new UserAccount("lisi",20000);
ITransfer transfer = new SafeOperateToo();//new SafeOperate or new TransferAccount
TransferThread zhangsanToLisi = new TransferThread("zhangsanToLisi",zhangsan,lisi,2000,transfer);
TransferThread lisiToZhangsan = new TransferThread("lisiToZhangsan",lisi,zhangsan,4000,transfer);
zhangsanToLisi.start();
lisiToZhangsan.start();
}
}
我的代码 github 地址: https://github.com/theodore816/javastudy/tree/master/com/enjoy/MultiThread/ch7
参考文献
https://blog.csdn.net/amd123456789/article/details/80867948
https://blog.csdn.net/u011116672/article/details/51051352
https://blog.csdn.net/w1014074794/article/details/51114752
https://www.cnblogs.com/hadoop-dev/p/6899171.html
https://www.cnblogs.com/baizhanshi/p/5437933.html
网友评论