• 【操作系统之二】死锁


    1、概念
    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
    概括来说:
      当前线程拥有其他线程需要的资源;
      当前线程等待其他线程已拥有的资源;
      都不放弃自己拥有的资源;

    2、产生条件
    1)互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
    2)请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
    3)不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
    4)循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

    3、三种锁
    (1)锁顺序死锁

    package com;
    
    public class LeftRightDeadlock {
        
        private final Object left = new Object();
        private final Object right = new Object();
    
        public void leftRight() {
            // 得到left锁
            synchronized (left) {
                // 得到right锁
                synchronized (right) {
                    System.out.println("do something....");
                }
            }
        }
    
        public void rightLeft() {
            // 得到right锁
            synchronized (right) {
                // 得到left锁
                synchronized (left) {
                    System.out.println("do something....");
                }
            }
        }
    
    }
    View Code

    线程是交错执行的,那么有可能出现:
    线程A调用leftRight()方法,得到left锁;
    线程B调用rightLeft()方法,得到right锁;
    线程A和线程B都继续执行,此时线程A需要right锁才能继续往下执行,线程B需要left锁才能继续往下执行。
    但是:线程A的left锁并没有释放,线程B的right锁也没有释放-->死锁


    (2)动态锁顺序死锁

    // 转账
        public static void transferMoney(Account fromAccount, Account toAccount,DollarAmount amount) throws InsufficientFundsException {
    
            // 锁定转出账户
            synchronized (fromAccount) {
                // 锁定转入账户
                synchronized (toAccount) {
                    // 判余额是否大于0
                    if (fromAccount.getBalance().compareTo(amount) < 0) {
                        throw new InsufficientFundsException();
                    } else {
                        // 汇账账户减钱
                        fromAccount.debit(amount);
                        // 来账账户增钱
                        toAccount.credit(amount);
                    }
                }
            }
        }
    View Code

    如果两个线程A和B同时调用transferMoney():
    线程A从X账户向Y账户转账;
    线程B从账户Y向账户X转账
    那么就会发生死锁。

    (3)协作对象之间发生死锁

    public class CooperatingDeadlock {
        // Warning: deadlock-prone!
        class Taxi {
            @GuardedBy("this") private Point location, destination;
            private final Dispatcher dispatcher;
    
            public Taxi(Dispatcher dispatcher) {
                this.dispatcher = dispatcher;
            }
    
            public synchronized Point getLocation() {
                return location;
            }
    
            // setLocation 需要Taxi内置锁
            public synchronized void setLocation(Point location) {
                this.location = location;
                if (location.equals(destination))
                    // 调用notifyAvailable()需要Dispatcher内置锁
                    dispatcher.notifyAvailable(this);
            }
    
            public synchronized Point getDestination() {
                return destination;
            }
    
            public synchronized void setDestination(Point destination) {
                this.destination = destination;
            }
        }
    
        class Dispatcher {
            @GuardedBy("this") private final Set<Taxi> taxis;
            @GuardedBy("this") private final Set<Taxi> availableTaxis;
    
            public Dispatcher() {
                taxis = new HashSet<Taxi>();
                availableTaxis = new HashSet<Taxi>();
            }
    
            public synchronized void notifyAvailable(Taxi taxi) {
                availableTaxis.add(taxi);
            }
    
            // 调用getImage()需要Dispatcher内置锁
            public synchronized Image getImage() {
                Image image = new Image();
                for (Taxi t : taxis)
                    // 调用getLocation()需要Taxi内置锁
                    image.drawMarker(t.getLocation());
                return image;
            }
        }
    
        class Image {
            public void drawMarker(Point p) {
            }
        }
    }
    View Code

    上面的getImage()和setLocation(Point location)都需要获取两个锁;
    并且在操作途中是没有释放锁的;
    这就是隐式获取两个锁(对象之间协作),这种方式也很容易就造成死锁。

    4、预防死锁
    针对产生条件
    资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
    只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
    可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
    资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

    (1)固定锁顺序避免死锁(针对锁顺序死锁)

    public class InduceLockOrder {
    
        // 额外的锁、避免两个对象hash值相等的情况(即使很少)
        private static final Object tieLock = new Object();
    
        public void transferMoney(final Account fromAcct,
                                  final Account toAcct,
                                  final DollarAmount amount)
                throws InsufficientFundsException {
            class Helper {
                public void transfer() throws InsufficientFundsException {
                    if (fromAcct.getBalance().compareTo(amount) < 0)
                        throw new InsufficientFundsException();
                    else {
                        fromAcct.debit(amount);
                        toAcct.credit(amount);
                    }
                }
            }
            // 得到锁的hash值
            int fromHash = System.identityHashCode(fromAcct);
            int toHash = System.identityHashCode(toAcct);
    
            // 根据hash值来上锁
            if (fromHash < toHash) {
                synchronized (fromAcct) {
                    synchronized (toAcct) {
                        new Helper().transfer();
                    }
                }
    
            } else if (fromHash > toHash) {// 根据hash值来上锁
                synchronized (toAcct) {
                    synchronized (fromAcct) {
                        new Helper().transfer();
                    }
                }
            } else {// 额外的锁、避免两个对象hash值相等的情况(即使很少)
                synchronized (tieLock) {
                    synchronized (fromAcct) {
                        synchronized (toAcct) {
                            new Helper().transfer();
                        }
                    }
                }
            }
        }
    }
    View Code

    得到对应的hash值来固定加锁的顺序,这样就不会发生死锁!


    (2)开放调用(针对对象之间协作造成的死锁)

    在协作对象之间发生死锁的例子中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法!
    如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用!

    改造:
    同步代码块最好仅被用于保护那些涉及共享状态的操作!

    class CooperatingNoDeadlock {
        @ThreadSafe
        class Taxi {
            @GuardedBy("this") private Point location, destination;
            private final Dispatcher dispatcher;
    
            public Taxi(Dispatcher dispatcher) {
                this.dispatcher = dispatcher;
            }
    
            public synchronized Point getLocation() {
                return location;
            }
    
            public synchronized void setLocation(Point location) {
                boolean reachedDestination;
    
                // 加Taxi内置锁
                synchronized (this) {
                    this.location = location;
                    reachedDestination = location.equals(destination);
                }
                // 执行同步代码块后完毕,释放锁
    
                if (reachedDestination)
                    // 加Dispatcher内置锁
                    dispatcher.notifyAvailable(this);
            }
    
            public synchronized Point getDestination() {
                return destination;
            }
    
            public synchronized void setDestination(Point destination) {
                this.destination = destination;
            }
        }
    
        @ThreadSafe
        class Dispatcher {
            @GuardedBy("this") private final Set<Taxi> taxis;
            @GuardedBy("this") private final Set<Taxi> availableTaxis;
    
            public Dispatcher() {
                taxis = new HashSet<Taxi>();
                availableTaxis = new HashSet<Taxi>();
            }
    
            public synchronized void notifyAvailable(Taxi taxi) {
                availableTaxis.add(taxi);
            }
    
            public Image getImage() {
                Set<Taxi> copy;
    
                // Dispatcher内置锁
                synchronized (this) {
                    copy = new HashSet<Taxi>(taxis);
                }
                // 执行同步代码块后完毕,释放锁
    
                Image image = new Image();
                for (Taxi t : copy)
                    // 加Taix内置锁
                    image.drawMarker(t.getLocation());
                return image;
            }
        }
    
        class Image {
            public void drawMarker(Point p) {
            }
        }
    
    }
    View Code

    使用开放调用是非常好的一种方式,应该尽量使用它.


    (3)使用定时锁-->tryLock()
    tryLock 是防止自锁的一个重要方式。tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

    package com;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test2 {
    
        public static void main(String[] args) {
            
            System.out.println("开始");
            final Lock lock = new ReentrantLock();
            new Thread() {
                @Override
                public void run() {
                    String tName = Thread.currentThread().getName();
                    if (lock.tryLock()) {
                        System.out.println(tName + "获取到锁!");
                    } else {
                        System.out.println(tName + "获取不到锁!");
                        return;
                    }
                    try {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(tName + ":" + i);
                        }
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        System.out.println(tName + "出错了!!!");
                    } finally {
                        System.out.println(tName + "释放锁!!");
                        lock.unlock();
                    }
     
                }
            }.start();
     
            new Thread() {
                @Override
                public void run() {
                    String tName = Thread.currentThread().getName();
     
                    if (lock.tryLock()) {
                        System.out.println(tName + "获取到锁!");
                    } else {
                        System.out.println(tName + "获取不到锁!");
                        return;
                    }
     
                    try {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(tName + ":" + i);
                        }
     
                    } catch (Exception e) {
                        System.out.println(tName + "出错了!!!");
                    } finally {
                        System.out.println(tName + "释放锁!!");
                        lock.unlock();
                    }
                }
            }.start();
             
            System.out.println("结束");
        }
    }
    View Code

    运行结果:

    开始
    Thread-0获取到锁!
    Thread-0:0
    Thread-0:1
    Thread-0:2
    Thread-0:3
    Thread-0:4
    结束
    Thread-1获取不到锁!
    Thread-0释放锁!!

    5、死锁检测

    死锁示例:

    package com;
    
    public class Test2 {
    
        public static void main(String[] args) {
            final Object a = new Object();
            final Object b = new Object();
            Thread threadA = new Thread(new Runnable() {
                public void run() {
                    synchronized (a) {
                        try {
                            System.out.println("now i in threadA-locka");
                            Thread.sleep(1000l);
                            synchronized (b) {
                                System.out.println("now i in threadA-lockb");
                            }
                        } catch (Exception e) {
                            // ignore
                        }
                    }
                }
            });
         
            Thread threadB = new Thread(new Runnable() {
                public void run() {
                    synchronized (b) {
                        try {
                            System.out.println("now i in threadB-lockb");
                            Thread.sleep(1000l);
                            synchronized (a) {
                                System.out.println("now i in threadB-locka");
                            }
                        } catch (Exception e) {
                            // ignore
                        }
                    }
                }
            });
         
            threadA.start();
            threadB.start();
        }
    
        
    }
    View Code

    (1)Jstack命令

    jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

    第一步:jps 找到死锁程序PID

    C:UsersAdministrator>jps
    8256 Test2
    7188
    11244 Jps

    Test2 进程号是8256

    第二步:使用jstack打印进程8256堆栈信息

    C:UsersAdministrator>jstack -F 8256
    Attaching to process ID 8256, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.171-b11
    Deadlock Detection:
    
    Found one Java-level deadlock:
    =============================
    
    "Thread-0":
      waiting to lock Monitor@0x000000001763a5a8 (Object@0x00000000d5fbffd0, a java/
    lang/Object),
      which is held by "Thread-1"
    "Thread-1":
      waiting to lock Monitor@0x0000000017637d18 (Object@0x00000000d5fbffc0, a java/
    lang/Object),
      which is held by "Thread-0"
    
    Found a total of 1 deadlock.

    对比:使用jstack -l 8256,输出:

    Found one Java-level deadlock:
    =============================
    "Thread-1":
      waiting to lock monitor 0x0000000057827bb8 (object 0x00000000d5fbffc0, a java.
    lang.Object),
      which is held by "Thread-0"
    "Thread-0":
      waiting to lock monitor 0x000000005782a4f8 (object 0x00000000d5fbffd0, a java.
    lang.Object),
      which is held by "Thread-1"
    
    Java stack information for the threads listed above:
    ===================================================
    "Thread-1":
            at com.Test2$2.run(Test2.java:31)
            - waiting to lock <0x00000000d5fbffc0> (a java.lang.Object)
            - locked <0x00000000d5fbffd0> (a java.lang.Object)
            at java.lang.Thread.run(Thread.java:748)
    "Thread-0":
            at com.Test2$1.run(Test2.java:15)
            - waiting to lock <0x00000000d5fbffd0> (a java.lang.Object)
            - locked <0x00000000d5fbffc0> (a java.lang.Object)
            at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.

    上面红色部分表示死锁发生位置。

    (2)JConsole工具
    Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
    第一步:输入命令jconsole 回车

    C:UsersAdministrator>jconsole

    弹出窗体:

    选择对应线程8256的java应用,点击“连接”:

    第二步:点击 “不安全的连接”

    第三步:线程 选项卡,点击下面的“检测死锁”

    参考:
    多线程之死锁就是这么简单 

    本地win下JConsole监控远程linux下的JVM

  • 相关阅读:
    SQLServer 事物与索引
    SQLServer 常见SQL笔试题之语句操作题详解
    测试思想-测试设计 测试用例设计之边界值分析方法
    测试思想-测试设计 测试用例设计之等价类划分方法
    测试思想-测试设计 测试用例设计之因果图方法
    测试思想-测试设计 测试用例设计之判定表驱动分析方法
    MySql 缓存查询原理与缓存监控 和 索引监控
    测试思想-测试设计 授客细说场景测试用例设计与实践
    产品相关 细说软件产品和业务 & 业务过程(流程) & 业务逻辑
    Postman Postman接口测试工具使用简介
  • 原文地址:https://www.cnblogs.com/cac2020/p/11753738.html
Copyright © 2020-2023  润新知