欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java并发-实例讲解死锁的产生,发现,解决。活锁,饥饿的概念

程序员文章站 2022-05-22 11:21:09
...

前言

死锁似乎是java面试或者笔试中必问的一个东西,还是需要搞清楚的,本文从什么是死锁,为什么死锁,如何解决死锁3个角度来描述

什么是死锁

当有两个或更多的线程在等待对方释放锁并无限期地卡住时,这种情况就称为死锁。
比如:
线程A,持有资源1,它只有获得资源2才能完成任务;
线程B,持有资源2,它只有获得资源1才能完成任务。
出现死锁原因,它们都想着获得对方手中的资源,但是却不肯放弃自己手上的资源

死锁的四种条件

1、互斥性:一个资源每次只能被一个进程使用。
2、请求和保持:一个进程请求新的资源的时候,不会释放已有的资源。
3、不剥夺条件:进程已有的资源在完成任务之前不能被剥夺。只能自己释放
4、循环等待条件:进程之间形成首尾相接的循环等待资源的关系。

死锁的小例子

package thread;

public class Test4 {
    public Object lock1= new Object();
    public Object lock2 = new Object();

    public static void main(String[] args) {
        Test4 test = new Test4();
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (test.lock1) {
                    System.out.println(Thread.currentThread().getName()+"获得了锁1");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    synchronized (test.lock2) {
                        System.out.println(Thread.currentThread().getName()+"获得了锁2");
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (test.lock2) {
                    System.out.println(Thread.currentThread().getName()+"获得了锁2");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    synchronized (test.lock1) {
                        System.out.println(Thread.currentThread().getName()+"获得了锁1");
                    }
                }

            }
        }).start();
    }
}

如何查看死锁?

常用的两种方法:
1、jstack

// jps查看进程号
aaa@qq.com_lyy:~$ jps
20419 Test4
20792 Jps
8606 org.eclipse.equinox.launcher_1.4.0.v20161219-1356.jar
// jstack + pid 
Java stack information for the threads listed above:
===================================================
"Thread-1":
    at thread.Test4$2.run(Test4.java:40)
    - waiting to lock <0x000000008c303768> (a java.lang.Object)
    - locked <0x000000008c303778> (a java.lang.Object)
    at java.lang.Thread.run(java.base@9.0.4/Thread.java:844)
"Thread-0":
    at thread.Test4$1.run(Test4.java:22)
    - waiting to lock <0x000000008c303778> (a java.lang.Object)
    - locked <0x000000008c303768> (a java.lang.Object)
    at java.lang.Thread.run(java.base@9.0.4/Thread.java:844)

Found 1 deadlock.

2、jconsole
java并发-实例讲解死锁的产生,发现,解决。活锁,饥饿的概念

死锁的解决

1、注意加锁的顺序

package thread;

public class Test4 {
    public Object lock1= new Object();
    public Object lock2 = new Object();

    public static void main(String[] args) {
        Test4 test = new Test4();
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (test.lock1) {
                    System.out.println(Thread.currentThread().getName()+"获得了锁1");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    synchronized (test.lock2) {
                        System.out.println(Thread.currentThread().getName()+"获得了锁2");
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (test.lock1) {
                    System.out.println(Thread.currentThread().getName()+"获得了锁1");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    synchronized (test.lock2) {
                        System.out.println(Thread.currentThread().getName()+"获得了锁2");
                    }
                }

            }
        }).start();
    }
}

2、避免无限等待
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) 方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

package thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Test4 {
    ReentrantLock lock1 = new ReentrantLock();
    ReentrantLock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Test4 test = new Test4();
        new Thread(new Runnable() {

            @Override
            public void run() {
                test.lock1.lock();
                if (test.lock1.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + "获得了锁1");
                }
                try {
                    Thread.sleep(300);
                    test.lock2.tryLock(200, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (test.lock2.isHeldByCurrentThread()) {
                        System.out.println(Thread.currentThread().getName() + "获得了锁2");
                        test.lock2.unlock();
                    }
                }
                test.lock1.unlock();
            }
        }, "A").start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                test.lock2.lock();
                if (test.lock2.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + "获得了锁2");
                }
                try {
                    Thread.sleep(300);
                    test.lock1.tryLock(300, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if (test.lock1.isHeldByCurrentThread()) {
                        System.out.println(Thread.currentThread().getName() + "获得了锁1");
                        test.lock1.unlock();
                    }
                }
                test.lock2.unlock();
            }
        }, "B").start();
    }
}
----output----
A获得了锁1
B获得了锁2
B获得了锁1

注意,这里finally里面的部分不能少,不然释放了锁之后,用unlock解锁就会报错,你没有这个锁 却去解锁,自然会报错。

什么是活锁

T1能使用资源A,但是它比较礼貌,让给了T2,T2此时能使用资源A,但是它也很绅士,选择让给T1,于是就这么让来让去,循环下去

什么是饥饿

非公平锁的抢占机制就会可能导致饥饿,当T1线程占用了资源A,T2,T3,T4。。。都在等待锁的释放,当锁释放的时候,T3抢到了资源,之后又释放锁,T4抢到了资源。。。T2 总是抢不到。这就是饿死

总结

目前笔者能力范围之内 能找到的解锁方法就是这两种了,因该还有别的比较好的方法,但是我没能发现。作为一个基础了解一下还是挺好的

相关标签: 死锁