Java多线程之死锁详解

yizhihongxing

Java多线程之死锁详解

什么是死锁

死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局,若无外力作用,它们无法继续进行下去。

死锁的产生原因

死锁的产生通常由以下四个必要条件引起:

  1. 互斥条件: 资源不能被共享,只能被一个线程占用。
  2. 请求与保持条件: 线程已经保持了至少一个资源,并且当前正在请求另一个资源。
  3. 不剥夺条件: 资源不能强制性地被其他线程抢占,而只能由持有它的线程自行释放。
  4. 环路等待条件: 存在一种循环等待资源的情况,形成一个等待环路。

以上四种情况缺一不可,才会导致死锁。

如何避免死锁

避免死锁的方法一般有以下几种:

  1. 避免使用多个锁: 如果只使用一个锁,就不会有两个线程互相等待对方释放锁的情况。
  2. 按固定的顺序加锁: 保证所有线程按照同样的锁顺序获得锁,避免出现循环等待的情况。
  3. 超时机制: 在申请资源时,限定等待的时间,如果超过了这个时间还没有得到资源,就释放资源,这样就可以避免死锁。

示例一

下面是一个很简单的死锁示例:

public class DeadLockDemo {

    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                System.out.println("线程1获得了锁1");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("线程1获得了锁2");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                System.out.println("线程2获得了锁2");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("线程2获得了锁1");
                }
            }
        }).start();
    }
}

以上代码中,两个线程分别持有锁1和锁2。当线程1占用锁1并尝试占用锁2的同时,线程2同时占用了锁2,因此它们互相等待对方释放锁而无法继续执行,发生了死锁。

示例二

还有一种更为经典的死锁示例叫做“哲学家就餐问题”:

public class DiningPhilosophers {

    private static final int NUMBER_OF_PHILOSOPHERS = 5;
    private static final long SIMULATION_TIME_MS = 5000;

    public static void main(String[] args) {
        Fork[] forks = new Fork[NUMBER_OF_PHILOSOPHERS];

        for (int i = 0; i < NUMBER_OF_PHILOSOPHERS; i++) {
            forks[i] = new Fork();
        }

        Philosopher[] philosophers = new Philosopher[NUMBER_OF_PHILOSOPHERS];

        for (int i = 0; i < NUMBER_OF_PHILOSOPHERS; i++) {
            philosophers[i] = new Philosopher(forks[i], forks[(i + 1) % NUMBER_OF_PHILOSOPHERS]);
        }

        for (Philosopher philosopher : philosophers) {
            new Thread(philosopher::start).start();
        }

        try {
            Thread.sleep(SIMULATION_TIME_MS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (Philosopher philosopher : philosophers) {
            philosopher.setFull(true);
        }
    }
}

class Philosopher implements Runnable {

    private Fork leftFork;
    private Fork rightFork;
    private boolean isFull = false;

    public Philosopher(Fork leftFork, Fork rightFork) {
        this.leftFork = leftFork;
        this.rightFork = rightFork;
    }

    @Override
    public void run() {
        while (!isFull) {
            synchronized (leftFork) {
                System.out.println(Thread.currentThread().getName() + "拿起了左边的叉子");
                synchronized (rightFork) {
                    System.out.println(Thread.currentThread().getName() + "拿起了右边的叉子");
                    eat();
                }
            }
        }
    }

    private void eat() {
        System.out.println(Thread.currentThread().getName() + "开始吃饭");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "吃饭结束,放下叉子");
    }

    public void setFull(boolean full) {
        isFull = full;
    }
}

class Fork {

}

以上代码中,5位哲学家围坐在一张圆桌旁,每个人的左右两侧都放着一把叉子。每个人要吃饭时,必须先拿起自己左右两侧的叉子,才能开始吃饭。

当所有人同时拿起自己左侧的叉子,但右边的叉子都被拿走了,这时所有人都无法继续吃饭,因为没有人释放它手里的叉子,从而陷入了死锁状态。

总结

死锁是多线程编程中的高难点,需要我们在程序设计中多方考虑,才能避免这种情况的出现。在实际开发中,可以通过调试工具和线程转储进行排查和分析,找到引起死锁的具体原因。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java多线程之死锁详解 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • golang 限制同一时间的并发量操作

    下面是详细讲解“golang 限制同一时间的并发量操作”的完整攻略: 前置知识 在了解如何限制同一时间的并发量操作之前,我们需要先了解一些并发编程基础知识,包括 goroutine、channel、sync.WaitGroup 和 sync.Mutex。 goroutine:Go 语言的轻量级线程,可以在多个 goroutine 之间并发执行。 channe…

    多线程 2023年5月16日
    00
  • Java线程编程中Thread类的基础学习教程

    Java线程编程中Thread类的基础学习教程 什么是Java线程? 在计算机科学中,线程是进程中的一段指令执行路径;或者说是CPU调度的最小单位。与进程相比,线程更加轻量级,可以提高CPU利用效率,充分发挥计算机的计算能力。在Java中,线程是指实现了java.lang.Thread类或者java.lang.Runnable接口的对象。 Thread类的基…

    多线程 2023年5月16日
    00
  • Kotlin协程Job生命周期结构化并发详解

    下面是”Kotlin协程Job生命周期结构化并发详解”的完整攻略: Kotlin协程Job生命周期结构化并发详解 概述 Kotlin协程是一种非阻塞式的并发处理机制,它可以极大地简化并发编程。其中一个核心概念就是协程的Job,Job代表了协程的执行任务。在实际使用中,Job可以用来管理和控制协程的生命周期以及取消协程的执行。 本文将详细讲解Kotlin协程J…

    多线程 2023年5月17日
    00
  • mysql并发控制原理知识点

    MySQL并发控制原理知识点主要涉及事务、锁和隔离级别三个方面。 事务 事务是指一系列操作被视为一个单独的逻辑单元,在满足ACID(原子性、一致性、隔离性和持久性)四个特性的同时,要么全部执行成功,要么全部不执行。MySQL默认支持事务,可以通过begin、commit和rollback等语句进行控制。 锁 在MySQL中,锁分为共享锁和排他锁,共享锁是用于…

    多线程 2023年5月16日
    00
  • Java并发容器介绍

    Java并发容器介绍 在Java中,我们可以使用多个并发容器来实现线程安全和高效访问数据。这些容器提供了不同的功能,适用于不同的场景。 并发容器类型 Java中的并发容器主要可以分为以下几类: List: 例如CopyOnWriteArrayList,线程安全的List实现。 Set: 例如ConcurrentSkipListSet,线程安全的Set实现,具…

    多线程 2023年5月16日
    00
  • 超详细讲解Linux C++多线程同步的方式

    下面就来详细讲解一下“超详细讲解Linux C++多线程同步的方式”的完整攻略。 如何实现多线程同步 在 C++ 程序中,线程之间的竞争条件是十分常见的一种情况,因此必须采取一些措施来避免这种情况的发生。以下是通过锁和条件变量来实现多线程同步的两种方式。 一、使用锁来实现多线程同步 锁可以帮助控制并发还原竞争。具体来说,当一个线程拥有锁时,任何其他线程都不能…

    多线程 2023年5月17日
    00
  • 彻底搞懂Java多线程(二)

    下面详细讲解一下“彻底搞懂Java多线程(二)”的完整攻略。 1. 线程的基本操作 在Java中,线程是通过Thread类来创建和启动的。创建线程的过程就是创建一个Thread对象,然后通过调用该对象的start()方法来启动线程,如下所示: Thread thread = new Thread(); thread.start(); 默认情况下,新线程会与当…

    多线程 2023年5月17日
    00
  • Spring boot如何通过@Scheduled实现定时任务及多线程配置

    下面我将为您详细讲解 Spring Boot 如何通过 @Scheduled 实现定时任务及多线程配置。 什么是@Scheduled? @Scheduled 是 Spring 框架提供的用于定时执行任务的注解,通过它可以配置定时执行的任务的时间。我们可以通过该注解实现定时任务的执行。 如何使用@Scheduled ? 在使用 @Scheduled 注解之前,…

    多线程 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部