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

前言

死锁似乎是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查看进程号
dream@love_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
这里写图片描述

死锁的解决

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 总是抢不到。这就是饿死

总结

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

猜你喜欢

转载自blog.csdn.net/qq_41376740/article/details/80633096