精华内容
下载资源
问答
  • 并发死锁

    2016-03-07 10:44:00
    JVM可以检测到synchronized导致的死锁,考虑下面的代码: public class ClassA { public static String monitor1 = "monitor1"; public static String monitor2 = "monitor2"; public static void main...

    JVM可以检测到synchronized导致的死锁,考虑下面的代码:

    public class ClassA {
        public static String monitor1 = "monitor1";
        public static String monitor2 = "monitor2";
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                public void run() {
                    try {
                        synchronized (monitor1) {
                            System.out.println(Thread.currentThread().getName() + ": got monitor1");
                            Thread.sleep(5000);
                            synchronized (monitor2) {
                                System.out.println(Thread.currentThread().getName() + ": got monitor2");
                            }
                        }
                    } catch (InterruptedException e) {}
                }
            }, "I'm Thread1");
            thread1.start();
            Thread thread2 = new Thread(new Runnable() {
                public void run() {
                    try {
                        synchronized (monitor2) {
                            System.out.println(Thread.currentThread().getName() + ": got monitor2");
                            Thread.sleep(5000);
                            synchronized (monitor1) {
                                System.out.println(Thread.currentThread().getName() + ": got monitor1");
                            }
                        }
                    } catch (InterruptedException e) {}
                }
            }, "I'm Thread2");
            thread2.start();
        }
    }


    代码中有意制造了典型的死锁情况,即在持有资源的情况下请求新的资源,并且两个线程交叉请求资源,从而导致在特定情况下发生死锁。

    在JVM监测中可以明显看到死锁并可以准确定位造成死锁的代码,如下图:

    image

    继续进行Thread Dump,在Thread Dump中发现错误代码的位置,如下图:

    其中明确说明了造成死锁的原因。

    上面描述的死锁是有意制造的简单场景,实际系统中的情况通常复杂得多,因此在开发过程中synchronized的使用应该谨慎,因为死锁很容易监测出来,但无法在JVM运行过程中简单地处理死锁。

    Java开发过程中,无论是使用synchronized关键字,还是使用其他锁定机制(如java.util.concurrent.locks.*)中的方法,在编程错误的情况下都可能出现死锁。死锁是操作系统课程中的重要内容,在多线程环境中编程的程序员应该了解其概念及如何避免死锁。

    附:死锁产生的必要条件

    如果在系统中同时具备下面四个必要条件时,那么可能发生死锁。换句话说,只要下面四个条件有一个不具备,系统就不会出现死锁。

    • 互斥条件:即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如打印机就是一种独占资源,两份文档不能同时打印;
    • 不可抢占条件:进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放;
    • 占有且申请条件:进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源;
    • 循环等待条件:存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环;

    上面四个条件在死锁时会同时发生。也就是说,只要有一个必要条件不满足,则死锁就可以排除。

    转载于:https://www.cnblogs.com/xiongmaotailang/p/5249712.html

    展开全文
  • 死锁描述了两个或多个线程等待彼此而被永久阻塞的情况。 当多个线程需要相同的锁定但以不同的顺序获取时,会发生死锁。 Java多线程程序可能会遇到死锁状况,因为synchronized关键字会导致执行线程在等待与指定对象相...

    死锁描述了两个或多个线程等待彼此而被永久阻塞的情况。 当多个线程需要相同的锁定但以不同的顺序获取时,会发生死锁。 Java多线程程序可能会遇到死锁状况,因为synchronized关键字会导致执行线程在等待与指定对象相关联的锁定或监视时出现阻止情况。 看看下面一个例子。

    示例public class TestThread {

    public static Object Lock1 = new Object();

    public static Object Lock2 = new Object();

    public static void main(String args[]) {

    ThreadDemo1 T1 = new ThreadDemo1();

    ThreadDemo2 T2 = new ThreadDemo2();

    T1.start();

    T2.start();

    }

    private static class ThreadDemo1 extends Thread {

    public void run() {

    synchronized (Lock1) {

    System.out.println("Thread 1: Holding lock 1...");

    try { Thread.sleep(10); }

    catch (InterruptedException e) {}

    System.out.println("Thread 1: Waiting for lock 2...");

    synchronized (Lock2) {

    System.out.println("Thread 1: Holding lock 1 & 2...");

    }

    }

    }

    }

    private static class ThreadDemo2 extends Thread {

    public void run() {

    synchronized (Lock2) {

    System.out.println("Thread 2: Holding lock 2...");

    try { Thread.sleep(10); }

    catch (InterruptedException e) {}

    System.out.println("Thread 2: Waiting for lock 1...");

    synchronized (Lock1) {

    System.out.println("Thread 2: Holding lock 1 & 2...");

    }

    }

    }

    }

    }

    当您编译并执行上述程序时,会出现死锁情况,以下是程序生成的输出 -

    Thread 1: Holding lock 1...

    Thread 2: Holding lock 2...

    Thread 1: Waiting for lock 2...

    Thread 2: Waiting for lock 1...

    上述程序将永久挂起,因为两个线程都不能继续进行,等待彼此释放锁定,所以您可以按CTRL + C退出程序。

    死锁解决方案示例

    下面我们修改锁的顺序并运行相同的程序,看看这两个线程是否仍然相互等待 -

    实例public class TestThread {

    public static Object Lock1 = new Object();

    public static Object Lock2 = new Object();

    public static void main(String args[]) {

    ThreadDemo1 T1 = new ThreadDemo1();

    ThreadDemo2 T2 = new ThreadDemo2();

    T1.start();

    T2.start();

    }

    private static class ThreadDemo1 extends Thread {

    public void run() {

    synchronized (Lock1) {

    System.out.println("Thread 1: Holding lock 1...");

    try {

    Thread.sleep(10);

    }catch (InterruptedException e) {}

    System.out.println("Thread 1: Waiting for lock 2...");

    synchronized (Lock2) {

    System.out.println("Thread 1: Holding lock 1 & 2...");

    }

    }

    }

    }

    private static class ThreadDemo2 extends Thread {

    public void run() {

    synchronized (Lock1) {

    System.out.println("Thread 2: Holding lock 1...");

    try {

    Thread.sleep(10);

    }catch (InterruptedException e) {}

    System.out.println("Thread 2: Waiting for lock 2...");

    synchronized (Lock2) {

    System.out.println("Thread 2: Holding lock 1 & 2...");

    }

    }

    }

    }

    }

    所以只是改变锁的顺序防止程序进入死锁情况并完成以下结果 -

    Thread 1: Holding lock 1...

    Thread 1: Waiting for lock 2...

    Thread 1: Holding lock 1 & 2...

    Thread 2: Holding lock 1...

    Thread 2: Waiting for lock 2...

    Thread 2: Holding lock 1 & 2...

    上面的例子只是为了更容易理解这个解决死锁的概念,然而,这是一个复杂的概念,应该深入了解它,然后才能再开发应用程序来处理死锁情况。

    ¥ 我要打赏

    纠错/补充

    收藏

    加QQ群啦,易百教程官方技术学习群

    注意:建议每个人选自己的技术方向加群,同一个QQ最多限加 3 个群。

    展开全文
  • 并发死锁问题

    1 什么是死锁

    • 发生在并发
    • 互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁。

     

    如果个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发生死锁 


    2 死锁的影响

    死锁的影响在不同系统中是不一样的,这取决于系统对死锁的处理能力

    • 数据库中:检测并放弃事务
    • JVM中:无法自动处理

    3 几率不高但危害大

    • 不一定发生,但是遵守 “墨菲定律
    • 一旦发生,多是高并发场景,影响用户多
    • 整个系统崩溃、子系统崩溃、性能降低
    • 压力测试无法找出所有潜在的死锁

    4 发生死锁的例子

    4.1 最简单的情况

    /**
     * 必定发生死锁的情况
     */
    public class MustDeadLock implements Runnable {
        static final Object LOCK_A = new Object();
        static final Object LOCK_B = new Object();
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"进入了run方法...");
            if ("A".equals(Thread.currentThread().getName())) {
                synchronized (LOCK_A) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (LOCK_B) {
                        System.out.println("线程A成功拿到两把锁");
                    }
                }
            }
            if ("B".equals(Thread.currentThread().getName())) {
                synchronized (LOCK_B) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (LOCK_A) {
                        System.out.println("线程B成功拿到两把锁");
                    }
                }
            }
        }
        public static void main(String[] args) {
            MustDeadLock run = new MustDeadLock();
            Thread t1 = new Thread(run, "A");
            Thread t2 = new Thread(run, "B");
            t1.start();
            t2.start();
        }
    }
    /**
     * jps -l
     * jstack 进程号
     */
    public class HoldLockThread implements Runnable {
        private final Object lockA;
        private final Object lockB;
        public HoldLockThread(Object lockA, Object lockB) {
            this.lockA = lockA;
            this.lockB = lockB;
        }
        @Override
        public void run() {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + " 自己持有" + lockA + ",尝试获得" + lockB);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException ignored) {
                }
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "自己持有" + lockB + ",尝试获得" + lockA);
                }
            }
        }
        public static void main(String[] args) {
            Object lockA = "lockA";
            Object lockB = "lockB";
            new Thread(new HoldLockThread(lockA, lockB), "AAA").start();
            new Thread(new HoldLockThread(lockB, lockA), "BBB").start();
        }
    }

    4.2 实际生产中的例子:转账

    • 需要把锁
    • 获取两把锁成功,且余额大于0,则扣除转出人,增加收款人的余额,是原子操作
    • 顺序相反导致死锁
    /**
     * 转账时候遇到死锁,一旦打开注释,便会发生死锁
     */
    public class TransferMoney {
        //A的账户余额
        static Account a = new Account(500);
        //B的账户余额
        static Account b = new Account(500);
        /**
         * 转账业务
         * @param from   钱从哪里来
         * @param to     钱到哪里去
         * @param amount 转账金额
         */
        public static void transferMoney(Account from, Account to, int amount) {
            //注意这里的加锁对象是谁
            synchronized (from) {
                //try {
                //    Thread.sleep(500);
                //} catch (InterruptedException e) {
                //    e.printStackTrace();
                //}
                synchronized (to) {
                    if (from.balance - amount < 0) {
                        System.out.println(Thread.currentThread().getName() + "余额不足,转账失败。");
                    }
                    from.balance -= amount;
                    to.balance += amount;
                    System.out.println(Thread.currentThread().getName() + "成功转账" + amount + "元");
                }
            }
        }
        //账号类
        static class Account {
            //余额
            int balance;
            public Account(int balance) {
                this.balance = balance;
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Runnable run = () -> {
                //A->B
                if ("A".equals(Thread.currentThread().getName())) {
                    transferMoney(a, b, 200);
                }
                //B->A
                if ("B".equals(Thread.currentThread().getName())) {
                    transferMoney(b, a, 300);
                }
            };
            Thread t1 = new Thread(run, "A");
            Thread t2 = new Thread(run, "B");
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("a的余额" + a.balance);
            System.out.println("b的余额" + b.balance);
        }
    }

    4.3 模拟多人随机转账

    5万人很多,但是依然会发生死锁,墨菲定律

    复习:发生死锁几率不高危害大

    public class TransferMoney {
        /**
         * 转账业务
         * @param from   钱从哪里来
         * @param to     钱到哪里去
         * @param amount 转账金额
         */
        public static void transferMoney(Account from, Account to, int amount) {
            //注意这里的加锁对象是谁
            synchronized (from) {
                synchronized (to) {
                    if (from.balance - amount < 0) {
                        System.out.println(Thread.currentThread().getName() + "余额不足,转账失败。");
                    }
                    from.balance -= amount;
                    to.balance += amount;
                    System.out.println(Thread.currentThread().getName() + "成功转账" + amount + "元");
                }
            }
        }
        //账号类
        static class Account {
            //余额
            int balance;
            public Account(int balance) {
                this.balance = balance;
            }
        }
    }
    /**
     * 多人同时转账,依然很危险
     */
    public class MultiTransferMoney {
        public static void main(String[] args) {
            //账户数量(数量越大发生死锁机率越小)
            int numAccounts = 5000;
            //每个账户的初始金额
            int numMoney = 1000;
            //转账次数(次数越小发生死锁机率越小)
            int numIterations = 1000000;
            //多少个人模拟转账
            int numThreads = 20;
            Random rnd = new Random();
            Account[] accounts = new Account[numAccounts];
            //初始化账户
            for (int i = 0; i < accounts.length; i++) {
                accounts[i] = new Account(numMoney);
            }
            Runnable run = () -> {
                //我转给你,你转给我的概率很小
                for (int i = 0; i < numIterations; i++) {
                    int fromAcct = rnd.nextInt(numAccounts);
                    int toAcct = rnd.nextInt(numAccounts);
                    int amount = rnd.nextInt(numMoney);
                    TransferMoney.transferMoney(accounts[fromAcct], accounts[toAcct], amount);
                }
                System.out.println("运行结束");
            };
            //模拟numThreads个人同时转账
            for (int i = 0; i < numThreads; i++) {
                new Thread(run).start();
            }
            //形成环了,卡死
        }
    }

    5 死锁的4个必要条件

    缺一不可,同时满足

    1. 互斥条件
    2. 请求与保持条件
    3. 不剥夺条件
    4. 循环等待条件

    6 如何定位死锁?

    6.1 jstack

    执行4.2 实际生产中的例子:转账 的代码

    1. 找到java程序对应的进程pid  jvisualvm

    2. 执行jstack命令 jstack 40732

     

    分析 4.3 模拟多人随机转账

     

    6.2 ThreadMXBean

    /**
     * 用ThreadMXBean检测死锁
     */
    public class ThreadMXBeanDetection implements Runnable {
        static final Object LOCK_A = new Object();
        static final Object LOCK_B = new Object();
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "进入了run方法...");
            if ("A".equals(Thread.currentThread().getName())) {
                synchronized (LOCK_A) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (LOCK_B) {
                        System.out.println("线程A成功拿到两把锁");
                    }
                }
            }
            if ("B".equals(Thread.currentThread().getName())) {
                synchronized (LOCK_B) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (LOCK_A) {
                        System.out.println("线程B成功拿到两把锁");
                    }
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            ThreadMXBeanDetection run = new ThreadMXBeanDetection();
            Thread t1 = new Thread(run, "A");
            //System.out.println("线程A,id=" + t1.getId());
            Thread t2 = new Thread(run, "B");
            //System.out.println("线程B,id=" + t2.getId());
            t1.start();
            t2.start();
            //让它进入死锁
            Thread.sleep(1000);
            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            //发现死锁的线程
            long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
            if (deadlockedThreads != null && deadlockedThreads.length > 0) {
                for (int i = 0; i < deadlockedThreads.length; i++) {
                    ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
                    System.out.println("发现死锁:" + threadInfo.getThreadName() + " id:" + deadlockedThreads[i]);
                }
            }
        }
    }

     

    7 修复死锁的策略

    线上问题都需要防患于未然,不造成损失地扑灭几乎已经是不可能

    保存案发现场然后立刻重启服务器

    暂时保证线上服务的安全,然后在利用刚才保存的信息,排查死锁,修改代码,重新发版

    7.1 避免策略 

    避免策略:哲学家就餐的换手方案、转账换序方案

    思路:避免相反的获取锁的顺序

    • 转账时避免死锁
    • 实际上不在乎获取锁的顺序
    • 通过hashcode来决定获取锁的顺序、冲突时需要 “加时赛
    • 主键就更方便

    public class TransferMoney {
        //A的账户余额
        static Account a = new Account(500);
        //B的账户余额
        static Account b = new Account(500);
        static final Object LOCK = new Object();
        /**
         * 转账业务
         * @param from   钱从哪里来
         * @param to     钱到哪里去
         * @param amount 转账金额
         */
        public static void transferMoney(Account from, Account to, int amount) {
            class Helper {
                public void transfer() {
                    if (from.balance - amount < 0) {
                        System.out.println(Thread.currentThread().getName() + "余额不足,转账失败。");
                        return;
                    }
                    from.balance -= amount;
                    to.balance += amount;
                    System.out.println(Thread.currentThread().getName() + "成功转账" + amount + "元");
                }
            }
            //获取锁的顺序一致
            //AB转账,A的hash值小于B
            //A->B 先获取A的锁,在获取B的锁
            //B->A 先获取A的锁,在获取B的锁
            int fromHash = System.identityHashCode(from);
            int toHash = System.identityHashCode(to);
            if (fromHash < toHash) {
                synchronized (from) {
                    synchronized (to) {
                        new Helper().transfer();
                    }
                }
            } else if (fromHash > toHash) {
                synchronized (to) {
                    synchronized (from) {
                        new Helper().transfer();
                    }
                }
            }
            //通过hashcode来决定获取锁的顺序、冲突时需要 “加时赛”
            else {
                synchronized (LOCK) {
                    synchronized (to) {
                        synchronized (from) {
                            new Helper().transfer();
                        }
                    }
                }
            }
        }
        //账号类
        static class Account {
            //余额
            int balance;
    
            public Account(int balance) {
                this.balance = balance;
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                //A->B
                transferMoney(a, b, 200);
            }, "A");
            Thread t2 = new Thread(() -> {
                //B->A
                transferMoney(b, a, 300);
            }, "B");
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("a的余额" + a.balance);
            System.out.println("b的余额" + b.balance);
        }
    }

    代码演示:哲学家进入死锁

    多种解决方案

    死锁资源耗尽的风险

    1. 服务员检查(避免策略)
    2. 改变—个哲学家拿叉子的顺序(避免策略)
    3. 餐票(避免策略)
    4. 领导调节(检测与恢复策略)
    /**
     * 演示哲学家就餐问题导致的死锁
     */
    public class DiningPhilosophers {
        //哲学家
        public static class Philosopher implements Runnable {
            //左边筷子
            private Object leftChopstick;
            //右边筷子
            private Object rightChopstick;
            public Philosopher(Object leftChopstick, Object rightChopstick) {
                this.leftChopstick = leftChopstick;
                this.rightChopstick = rightChopstick;
            }
            @Override
            public void run() {
                try {
                    while (true) {
                        //思考
                        doAction("Thinking");
                        synchronized (leftChopstick) {
                            //拿起左边的筷子
                            doAction("Picked up left chopstick");
                            synchronized (rightChopstick) {
                                //拿起右边的筷子
                                doAction("Picked up right chopstick - eating");
                                //放下右边的筷子
                                doAction("Put down right chopstick");
                            }
                            //放下左边的筷子
                            doAction("Put down left chopstick");
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //动作:打印+等待
            private void doAction(String action) throws InterruptedException {
                System.out.println(Thread.currentThread().getName() + " " + action);
                //[0,10)
                Thread.sleep((long) (Math.random() * 10));
            }
        }
        public static void main(String[] args) {
            //哲学家数组
            Philosopher[] philosophers = new Philosopher[5];
            //筷子数组 筷子跟数==哲学家人数
            Object[] chopsticks = new Object[philosophers.length];
            //初始化
            for (int i = 0; i < chopsticks.length; i++) {
                chopsticks[i] = new Object();
            }
            for (int i = 0; i < philosophers.length; i++) {
                //左筷子
                Object leftChopstick = chopsticks[i];
                //右筷子
                Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
                //1.发生死锁 都是Picked up left chopstick
                //philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
                
                //2.改变—个哲学家拿叉子的顺序(避免策略)
                //让最后一个哲学家,反着拿
                if (i == philosophers.length - 1) {
                    philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
                } else {
                    philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
                }
                new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
            }
        }
    }

    7.2 检测与恢复策略 

    检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁

    7.3 鸵鸟策略

    鸵鸟策略:鸵鸟这种动物在遇到危险的时候,通常就会把头埋在地上,这样一来它就看不到危险了。而鸵鸟策略的意思就是说,如果我们发生死锁的概率极其低,那么我们就直接忽略它,直到死锁发生的时候,再人工修复


    8 实际工程中如何避免死锁

    • Lock的tryLock(long time, TimeUnit unit)
    • synchronized不具备尝试锁的能力
    • 造成超时的可能性多:发生了死锁、线程陷入死循环、线程执行很慢
    • 获取锁失败:打日志、发报警邮件、重启

    代码演示:退一步海阔天空

    /**
     * 用tryLock来避免死锁
     */
    public class TryLockDeadlock implements Runnable {
        static Lock lock1 = new ReentrantLock();
        static Lock lock2 = new ReentrantLock();
        @Override
        public void run() {
            //尝试获取次数
            for (int i = 0; i < 100; i++) {
                if ("A".equals(Thread.currentThread().getName())) {
                    try {
                        if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                            System.out.println("线程A获取到了锁1");
                            Thread.sleep(new Random().nextInt(1000));
                            if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                                System.out.println("线程A获取到了锁2");
                                System.out.println("线程A成功获取到了两把锁");
                                //do something
                                lock2.unlock();
                                lock1.unlock();
                                break;
                            } else {
                                System.out.println("线程A尝试获取锁2失败,已重试");
                                lock1.unlock();
                                Thread.sleep(new Random().nextInt(1000));
                            }
                        } else {
                            System.out.println("线程A获取锁1失败,已重试");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if ("B".equals(Thread.currentThread().getName())) {
                    try {
                        if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
                            System.out.println("线程B获取到了锁2");
                            Thread.sleep(new Random().nextInt(1000));
                            if (lock1.tryLock(3000, TimeUnit.MILLISECONDS)) {
                                System.out.println("线程B获取到了锁1");
                                System.out.println("线程B成功获取到了两把锁");
                                //do something
                                lock1.unlock();
                                lock2.unlock();
                                break;
                            } else {
                                System.out.println("线程B尝试获取锁1失败,已重试");
                                lock2.unlock();
                                Thread.sleep(new Random().nextInt(1000));
                            }
                        } else {
                            System.out.println("线程B获取锁2失败,已重试");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        public static void main(String[] args) {
            TryLockDeadlock run = new TryLockDeadlock();
            new Thread(run,"A").start();
            new Thread(run,"B").start();
        }
    }

    9 其他活性故障

    • 死锁是最常见的活跃性问题,不过除了死锁之外,还有一些类似的问题,会导致程序无法顺利执行,统称为活跃性问题
    • 活锁(LiveLock)
    • 饥饿

    9.1 活锁

    • 死锁:每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)
    • 活锁:在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉
    • 在实际的计算机问题中,缺乏餐叉可以类比为缺乏共享资源

    9.1.2 什么是活锁

    • 虽然线程并没有阻塞,也始终在运行(所以叫做 “活” 锁,线程是 “活” 的),但是程序却得不到进展,因为线程始终重复做同样的事
    • 如果下图中描述的是 “死锁”,那么这里的情况就是,两个人都始终一动不动,直到对方先抬头,他们之间不再说话了,只是等待
    • 如果下图中描述的是 “活锁”,那么这里的情况就是,双方都不停地对对方说 “你先起来吧,你先起来吧” ,双方都一直在说话,在运行
    • 死锁和活锁的结果是一样的,就是谁都不能先抬头

    9.1.3 代码演示

    /**
     * 演示活锁问题
     */
    public class LiveLock {
        //餐具:勺子(只有一个)
        static class Spoon {
            //勺子拥有者
            private Diner owner;
            public Spoon(Diner owner) {
                this.owner = owner;
            }
            public void setOwner(Diner owner) {
                this.owner = owner;
            }
            //使用勺子
            public synchronized void use() {
                System.out.printf("%s吃完了!", owner.name);
            }
        }
        //就餐者:正在吃饭的那个人
        static class Diner {
            //就餐者名字
            private String name;
            //就餐者是否饥饿
            private boolean isHungry;
            public Diner(String name) {
                this.name = name;
                //初始化饥饿
                isHungry = true;
            }
            /**
             * 就餐方法
             * @param spoon 餐具(物资匮乏,只有一个)
             * @param spouse 陪我一起就餐的人
             */
            public void eatWith(Spoon spoon, Diner spouse) {
                while (isHungry) {
                    //发现勺子拥有者不是自己,伴侣可能正在用
                    if (spoon.owner != this) {
                        try {
                            //等伴侣吃完
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        continue;
                    }
                    //已经拿到勺子了
                    //开始谦让,先询问对方是否饥饿
    
                    //!!!发生活锁,互相谦让,把勺子给对方
                    //if (spouse.isHungry) {
    
                    //原因:重试机制不变,消息队列始终重试,吃饭始终谦让
                    //解决:加入随机因素(以太网的指数退避算法)
                    if (spouse.isHungry && new Random().nextInt(10) < 9) {
                        System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");
                        //勺子给对方
                        spoon.setOwner(spouse);
                        continue;
                    }
                    //对方不饿,自己吃
                    spoon.use();
                    //不在饥饿
                    isHungry = false;
                    System.out.println(name + ": 我吃完了");
                    //吃完了,勺子给对方
                    spoon.setOwner(spouse);
                }
            }
        }
        public static void main(String[] args) {
            Diner husband = new Diner("药水哥");
            Diner wife = new Diner("周淑怡");
            //物资匮乏,只有一个勺子
            Spoon spoon = new Spoon(husband);
            new Thread(() -> husband.eatWith(spoon, wife)).start();
            new Thread(() -> wife.eatWith(spoon, husband)).start();
        }
    }

    9.1.4 工程中的活锁实例:消息队列

    • 策略:消息如果处理失败,就放在队列开头重试
    • 由于依赖服务出了问题,处理该消息一直失败
    • 没阻塞,但程序无法继续
    • 解决:放到队列尾部、重试限制

    9.1.5 如何解决活锁问题

    加入随机因素


    9.2 饥饿

    当线程需要某些资源(例如CPU),但是却始终得不到

    线程的优先级设置得过于低,或者有某线程持有锁同时又无限循环从而不释放锁,或者某程序始终占用某文件的写锁

    饥饿可能会导致响应性差:比如,我们的浏览器有一个线程负责处理前台响应(打开收藏夹等动作),另外的后台线程负责下载图片和文件、计算渲染等。在这种情况下,如果后台线程把CPU资源都占用了,那么前台线程将无法得到很好地执行,这会导致用户的体验很差

    9.3 线程优先级 

    10个级别,默认5


    程序设计不应依赖于优先级

    • 不同操作系统不一样
    • 优先级会被操作系统改变

    10 常见面试问题

    1. 写一个必然死锁的例子,生产中什么场景下会发生死锁?

    • 在一个方法中获取多个锁,容易产生死锁

    2. 发生死锁必须满足哪些条件

    1. 互斥条件
    2. 请求与保持条件
    3. 不剥夺条件
    4. 循环等待条件

    3. 如何定位死锁?

    1. jstack
    2. ThreadMXBean

    4. 有哪些解决死锁问题的策略

    1. 避免策略:哲学家就餐的换手方案、转账换序方案
    2. 检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁
    3. 鸵鸟策略:鸵鸟这种动物在遇到危险的时候,通常就会把头埋在地上,这样一来它就看不到危险了。而鸵鸟策略的意思就是说,如果我们发生死锁的概率极其低,那么我们就直接忽略它,直到死锁发生的时候,再人工修复

    5.讲一讲经典的哲学家就餐问题

    1. 画出示意图
    2. 给出多种解决方案

    1. 服务员检查(避免策略)
    2. 改变一个哲学家拿叉子的顺序(避免策略)
    3. 餐票(避免策略)
    4. 领导调节(检测与恢复策略)

    6. 实际工程中如何避免死锁?

    1. 设置超时时间
    2. 多使用并发类而不是自己设计锁
    3. 尽量降低锁的使用粒度:用不同的锁而不是一个锁
    4. 如果能使用同步代码块,就不使用同步方法:自己指定锁对象
    5. 给你的线程起个有意义的名字:debug和排查时事半功倍,框架和JDK都遵守这个最佳实践
    6. 避免锁的嵌套:MustDeadLock类
    7. 分配资源前先看能不能收回来:银行家算法
    8. 尽量不要几个功能用同一把锁:专锁专用

    7. 什么是活跃性问题?活锁、饥饿和死锁有什么区别?

    展开全文
  • 数据越来越和我们的生活离不开,数据在生命周期的各个阶段有着不同的痛点和需求以及特殊场景。CURD是数据的四大基本...4.有哪些典型的高并发死锁场景?1.我们先来看看什么是死锁。在《数据库系统实现》第八章第二...

    数据越来越和我们的生活离不开,数据在生命周期的各个阶段有着不同的痛点和需求以及特殊场景。

    CURD是数据的四大基本需求:写入,更新,读取,删除.

    今天,来谈一谈死锁问题

    死锁是高并发下MySQL不可回避的一个问题。

    这句话可以引申四个问题:

    1.什么是死锁?

    2.MySQL什么时候会检测死锁?

    3.数据库系统如何处理死锁?

    4.有哪些典型的高并发死锁场景?

    1.我们先来看看什么是死锁。

    在《数据库系统实现》第八章第二节这样定义死锁

    并发执行的事务由于竞争资源而到达一个存在死锁的状态:若干事务的每一个事务都在等待被其他事务占用的资源,因而每个事务都不能取得进展。

    这个描述貌似很拗口,我们举两个例子来形象化认识一下:

    1.两位木匠钉地板,一位只握一把斧头,而另一位没有榔头,却有钉子

    2.堵车现象

    2b4af2b3f68574b5aae456363ce8fb0f.png

    看完死锁的定义描述和形象化认识,那对于MySQL,什么时候会进行死锁检测?

    2.MySQL的死锁检测和回滚

    这里谈论MySQL的死锁检测,目前仅讨论InnoDB的处理,暂不涉及MyRocks的死锁检测处理。

    当InnoDB事务尝试获取(请求)加一个锁,并且需要等待时,InnoDB会进行死锁检测.

    正常的流程如下:

    1.InnoDB的初始化一个事务,当事务尝试获取(请求)加一个锁,并且需要等待时(wait_lock),innodb会开始进行死锁检测(deadlock_mark)

    2.进入到lock_deadlock_check_and_resolve ,名字很明显了,要检测死锁和解决死锁

    3.检测死锁过程中,也是有计数器来进行限制的

    4.死锁检测的逻辑之一是等待图的处理过程,如果通过锁的信息和事务等待链构造出一个图,如果图中出现回路,就认为发生了死锁。

    5.死锁的回滚,内部代码的处理逻辑之一是比较undo的数量

    3.数据库系统如何处理死锁

    我们回头继续看《数据库系统实现》里面提到的死锁处理

    1.超时死锁检测:当存在死锁时,想所有事务都能同时继续执行通常是不可能的,因此,至少一个事务必须中止并重新开始。超时是最直接的办法,对超出活跃时间的事务进行限制和回滚

    2.等待图:等待图的实现,是可以表明哪些事务在等待其他事务持有的锁,可以在数据库的死锁检测里面加上这个机制来进行检测是否有环的形成。

    3.通过元素排序预防死锁:这个想法很美好,但现实很残酷,通常都是发现死锁后才去想办法解决死锁的原因

    4.通过时间戳检测死锁:对每个事务都分配一个时间戳,根据时间戳来进行回滚策略。

    这里贴一下等待图的示例

    a4180f4db73ff240d045655b1d417f76.png

    4.有哪些典型的高并发死锁场景?

    1.秒杀场景,每个秒杀都是针对同一行的活跃事务,源源不断的事务发现自己加锁的那一行已经被人锁了,这时候InnoDB会进入一个蛋疼的没必要的死锁检测,后续给大家讲讲怎么解决

    2.使用二级索引去高并发更新二级索引记录(很拗口吧?),MySQL的索引计划不是100%准确的,我手上有case在并发更新不同记录的时候,因为索引计划走错了,导致某一个事务用了二级索引读记录,另外一个事务用主键来读记录,进而产生了死锁,这个案例后续也会整理出来。

    最后 MySQL的源码如何进行死锁检测和处理?

    这个问题是后续的关键,但没整理完,先歇一歇...

    建议先读一读上一篇《InnoDB事务结构体代码变量列表》,因为死锁是在活跃事务等待锁的情况下才会去检测,要先去了解InnoDB事务结构体的trx_lock_t

    展开全文
  • JVM解决死锁问题方面,并不像数据库服务那么强大(数据库系统设计中考虑了检测死锁以及从死锁中恢复),当一组Java线程发生死锁时,"游戏"到此结束,这些线程永远不能再使用了。死锁的含义:当线程A持有锁L并想获得锁M...
  • mysql for update 高并发 死锁研究 mysql for update语句 https://www.cnblogs.com/jtlgb/p/8359266.html For update带来的思考 http://www.cnblog...
  • 目录死锁是什么?官方定义:死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁...
  • Java并发死锁

    2020-04-24 16:28:40
    简介:所谓死锁就是描述两个或多个线程因竞争资源出现等待彼此造成永久阻塞的情况,若无外力作用,形成一种“僵局”。 java 死锁产生的四个必要条件: 1、互斥条件:进程要求对所分配的资源进行排它性控制,即在一段...
  • java并发 死锁

    2018-08-20 21:22:43
    参考链接: http://tutorials.jenkov.com/java-concurrency/deadlock.html ... 学习小结 线程死锁 并发系统中经常需要对资源加锁,如果多个线程在同一时间对相同资源加锁时可能会发生死锁。但是如果多个线程...
  • 哲学家进餐问题并发执行带来的最棘手的问题莫过于死锁了,死锁问题中最经典的案例就是哲学家进餐问题:5个哲学家坐在一个桌子上,桌子上有5根筷子,每个哲学家的左手边和右手边各有一根筷子。示意图如下:哲学家进餐...
  • 事务1 - 插入ignore语句尝试对该行进行S锁定,但事务1的X锁定已经在等待导致死锁的锁定 所以,我想知道如何处理并发更新并将新事件(而不是行更新)插入到我的表中而不会导致死锁 . 1.锁定顺序应该是什么? 2.如何确保...
  • Mysql并发时经典常见的死锁原因更新时间:2017-06-07 00:17:211256次阅读评论01、mysql都有什么锁MySQL有三种锁的级别:页级、表级、行级。表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高...
  • 死锁是一种无限的互相等待的状态,两个或两个以上的线程或进程构成一个互相等待的环状。以两个线程为例,线程一持有A锁同时在等待B锁,而线程二持有B锁同时在等待A锁,这就导致两个线程互相等待无法往下执行。现实...
  • 目录1、前言2、数据库准备3、案例分析一、前言死锁问题是并发系统中绕不开的一个话题,同样在数据库MySQL存储引擎Innodb中并发情况下死锁问题也屡见不鲜。对于死锁定义、死锁产生的条件、各种锁之间的兼容关系、死锁...
  • 并发死锁产生的原因一般是由于加锁顺序不一致引起的,假设一个事物需要获得连续获得两个资源的锁,如果执行事物的两个线程获得这两个锁的顺序不一致,就有可能产生死锁。 下面是我画的一个简单的说明图:  在Dead...
  • 死锁发生的必要条件:1、互斥条件2、请求和保持条件3、资源不剥夺条件4、环路等待条件多线程的最佳并发实践;1、 使用本地变量2、使用不可变类3、最小化锁的作用域范围 : S = 1/(1-a+a/n) (阿木达尔定律)其中,a为...
  • 验证一些由于并发问题引起的死锁问题,需要用高并发发送请求来验证。我是采用gevent 来实现高并发的。在实际的使用过程中发现脚本并没有实现高并发进行,于是进行了测试验证,验证结果如下,例子采用网上通用的例子...
  • 死锁问题是并发系统中绕不开的一个话题,同样在数据库MySQL存储引擎Innodb中并发情况下死锁问题也屡见不鲜。对于死锁定义、死锁产生的条件、各种锁之间的兼容关系、死锁检测相关内容本篇文章不做深入介绍,本篇文章...
  • 一般根据多条件过滤后更新update在高并发的时候会导致死锁,进而事务失败。 这是因为mysql行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引...
  • 欢迎关注!获取更高质量的文章!1. mysql都有什么锁MySQL有三种锁的级别:页级、表级、行级。表级锁:开销小,加锁快;不会出现死锁;...锁定粒度界于表锁和行锁之间,并发度一般算法:next KeyLocks锁,同...
  • mysql并发死锁优化

    2018-05-15 21:01:00
    3.解决死锁:在命令行输入show engine innodb statusG可以显示最近死锁的信息,然后加以解决其次可以设置锁等待时间,这个参数innodb_lock_wait_timeout增加锁等待时间,即增大下面配置项参数值,单位为秒(s...
  • 深入浅出SQL Server中的死锁 SQL Server 死锁 (Page锁)诊断 SQL Server死锁总结
  • 我们在学习操作系统的时候,一定接触过哲学家就餐的问题,这是由Edsger Dijkstra提出的经典的死锁例子,该例子描述的是,指定五个哲学家一起就餐,这些哲学家花一部分时间就餐,一部分时间思考,就餐时,总共五把...
  • 多线程并发死锁

    千次阅读 2018-03-28 10:50:52
    一、线程并发同步概念线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。线程同步,就是当线程...
  • 1.进程先申请资源信号源,在申请互斥信号源,不能颠倒,不然可能产生死锁。 2.wait/signal必须配对使用,并且只能嵌套,不能交叉 3.同一个信号源的wait和signal可以在不同的进程中 4.wait和signal不能颠倒顺序,wiat...
  • 同时向同一个数据库的同一张表插入数据,用insert into 或者 load data infile 都会出现 锁索引的情况,造成死锁,哪些大神能够帮忙解决下。大大有赏。在线等,急! 下面是死锁的具体信息: -----------------------...
  • 对于java流操作,并发处理很大几率导致死锁,应注意流的输出。 比如: 类似这两种就极易导致接口默默超时,锁死。接口应注意类似操作,及时加锁,线程等待
  • 有问题的go并发代码
  • 发生在【并发】中 【互不相让】:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁。 多个线程造成死锁的情况(A->B->C-...
  • jackson的一个并发死锁的问题

    千次阅读 2017-06-06 19:11:20
    jackson的2.4.1死锁

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,359
精华内容 3,743
关键字:

并发死锁