技术分享

多线程之死锁和解决方案

2019-01-21  本文已影响8人  Theodore的技术站

什么是死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的 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

上一篇下一篇

猜你喜欢

热点阅读