java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java死锁和活锁的联系

Java死锁和活锁的联系及说明

作者:找不到、了

这篇文章主要介绍了Java死锁和活锁的联系及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

本章还是重点介绍下java开发过程中,会出现的锁。

在多线程编程中,死锁(Deadlock)活锁(Livelock)都是线程间协调不当导致的执行困境,但二者的表现和机制有显著区别。

并发编程中的常见问题

├── 活锁 (Livelock)
│   ├── 定义:两个或多个进程或线程相互之间不断尝试执行某个操作,但由于彼此的影响,这些操作都无法成功完成
│   ├── 特点:系统不会崩溃,但也不会取得任何进展
│   ├── 示例:线程A和线程B互相等待对方改变标志

├── 饥饿 (Starvation)
│   ├── 定义:某个线程或进程由于资源竞争或调度策略的原因,长时间无法获得必要的资源,导致其无法执行
│   ├── 特点:某些线程或进程始终得不到执行的机会
│   ├── 示例:低优先级任务始终无法执行

├── 无锁 (Lock-Free)
│   ├── 定义:不使用传统的互斥锁,而是使用原子操作和内存模型来实现线程安全
│   ├── 特点:提高并发性能,减少锁的竞争
│   ├── 示例:使用 AtomicInteger 实现线程安全的计数器

└── 死锁 (Deadlock)
    ├── 定义:两个或多个进程或线程在执行过程中,因争夺资源而造成的一种相互等待的现象
    ├── 特点:系统陷入停滞状态,无法继续执行
    ├── 示例:两个线程互相等待对方持有的锁

1、死锁

1、产生原因

满足下面四个必要条件,则会产生死锁现象。

1、互斥条件

线程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个线程占用。如果此时还有其它线程请求该资源,则请求者只能等待,直至占有资源的线程用毕释放。

2、请求和保持条件

线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放。

3、不剥夺条件

线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

4、循环等待条件

在发生死锁时,必然存在一个线程 —— 资源的环形链,即线程集合 {T0,T1,T2,・・・,Tn} 中的 T0 正在等待一个 T1 占用的资源;T1 正在等待 T2 占用的资源,……,Tn 正在等待已被 T0 占用的资源。

代码示例:

class DeadlockExample {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("Thread 1: Holding resource 1...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Waiting for resource 2...");
                synchronized (resource2) {
                    System.out.println("Thread 1: Holding resource 1 and 2...");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("Thread 2: Holding resource 2...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2: Waiting for resource 1...");
                synchronized (resource1) {
                    System.out.println("Thread 2: Holding resource 1 and 2...");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

代码解释

由上面可知,死锁的四个条件均满足。

2、死锁场景

3、解决方案

3.1. 预防

预防死锁

通过破坏产生死锁的四个必要条件中的一个或几个来预防死锁的发生。例如,一次性获取所有需要的资源,避免请求和保持条件;或者允许资源被剥夺。

避免死锁

在资源分配过程中,通过算法来判断是否会发生死锁,只有在不会发生死锁的情况下才进行资源分配。例如,银行家算法。

1. 顺序获取锁

方案:确保所有线程以相同的顺序获取锁,避免不同的锁获取顺序(最有效方案)。

public class DeadlockSolution1 {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread1 acquired lock1");
                // 添加小延迟,增加死锁概率
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                synchronized (lock2) {
                    System.out.println("Thread1 acquired lock2");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock1) {  // 改为先获取lock1,与thread1顺序一致
                System.out.println("Thread2 acquired lock1");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                synchronized (lock2) {
                    System.out.println("Thread2 acquired lock2");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

2. 使用 tryLock (带超时)

方案:使用 ReentrantLocktryLock() 方法,避免无限期等待,可以增加回退方案,并且超时情况下线程会释放掉锁。

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

public class DeadlockSolution2 {
    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) {
                    System.out.println("Thread1 acquired lock1");
                    try { Thread.sleep(100); } catch (InterruptedException e) {}
                    
                    if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) {
                        System.out.println("Thread1 acquired lock2");
                        lock2.unlock();
                    } else {
                        System.out.println("Thread1 failed to acquire lock2");
                    }
                    lock1.unlock();
                } else {
                    System.out.println("Thread1 failed to acquire lock1");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) {
                    System.out.println("Thread2 acquired lock2");
                    try { Thread.sleep(100); } catch (InterruptedException e) {}
                    
                    if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) {
                        System.out.println("Thread2 acquired lock1");
                        lock1.unlock();
                    } else {
                        System.out.println("Thread2 failed to acquire lock1");
                    }
                    lock2.unlock();
                } else {
                    System.out.println("Thread2 failed to acquire lock2");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();
    }
}

3. Phaser

方案:使用 java.util.concurrent 包中的高级工具类替代显式锁。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;

public class DeadlockSolution4 {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Phaser phaser = new Phaser(2); // 使用Phaser协调线程
        
        executor.submit(() -> {
            System.out.println("Task1 started");
            phaser.arriveAndAwaitAdvance(); // 等待其他线程
            System.out.println("Task1 completed");
        });
        
        executor.submit(() -> {
            System.out.println("Task2 started");
            phaser.arriveAndAwaitAdvance(); // 等待其他线程
            System.out.println("Task2 completed");
        });
        
        executor.shutdown();
    }
}

4. 银行家算法

四种数据结构

实现步骤:

1. 安全状态检查算法

boolean isSafeState() {
    // 1. 初始化
    int[] work = Arrays.copyOf(Available, Available.length);
    boolean[] finish = new boolean[processCount];
    Arrays.fill(finish, false);
    
    // 2. 寻找可满足的进程
    int count = 0;
    while (count < processCount) {
        boolean found = false;
        for (int i = 0; i < processCount; i++) {
            if (!finish[i] && checkNeedLessThanWork(i, work)) {
                // 3. 假设进程i释放资源
                for (int j = 0; j < resourceTypes; j++) {
                    work[j] += Allocation[i][j];
                }
                finish[i] = true;
                found = true;
                count++;
            }
        }
        if (!found) break; // 没有找到可满足的进程
    }
    
    // 4. 检查是否所有进程都能完成
    return count == processCount;
}

boolean checkNeedLessThanWork(int process, int[] work) {
    for (int i = 0; i < resourceTypes; i++) {
        if (Need[process][i] > work[i]) {
            return false;
        }
    }
    return true;
}

2. 资源请求算法

boolean requestResources(int process, int[] request) {
    // 1. 检查请求是否超过声明需求
    for (int i = 0; i < resourceTypes; i++) {
        if (request[i] > Need[process][i]) {
            return false;
        }
    }
    
    // 2. 检查系统是否有足够资源
    for (int i = 0; i < resourceTypes; i++) {
        if (request[i] > Available[i]) {
            return false;
        }
    }
    
    // 3. 尝试分配
    for (int i = 0; i < resourceTypes; i++) {
        Available[i] -= request[i];
        Allocation[process][i] += request[i];
        Need[process][i] -= request[i];
    }
    
    // 4. 检查安全性
    if (isSafeState()) {
        return true;
    } else {
        // 回滚分配
        for (int i = 0; i < resourceTypes; i++) {
            Available[i] += request[i];
            Allocation[process][i] -= request[i];
            Need[process][i] += request[i];
        }
        return false;
    }
}

银行家算法通过以下机制预防死锁:

事前预防:在资源分配前进行安全性检查

破坏死锁必要条件

动态决策

3.2. 检测

检测死锁

解除死锁

2、活锁

1、介绍

活锁是指线程不断改变状态来响应对方,但整体任务无法推进

线程未阻塞,仍在不断尝试执行操作,但因相互干扰陷入无效循环,CPU 利用率不为 0(线程在执行无意义的重试)。

2、场景

线程的调度由于不停切换,也会导致。

代码示例:

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

public class LivelockExample {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();

    public void worker1() {
        while (true) {
            lock1.lock();
            System.out.println("Worker1 获取 lock1");
            
            // 尝试获取 lock2,如果失败就释放 lock1 并重试
            if (lock2.tryLock()) {
                System.out.println("Worker1 获取 lock2,完成任务");
                lock2.unlock();
                lock1.unlock();
                break;
            } else {
                System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试");
                lock1.unlock();
            }
            
            // 短暂休眠,避免 CPU 100%
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public void worker2() {
        while (true) {
            lock2.lock();
            System.out.println("Worker2 获取 lock2");
            
            // 尝试获取 lock1,如果失败就释放 lock2 并重试
            if (lock1.tryLock()) {
                System.out.println("Worker2 获取 lock1,完成任务");
                lock1.unlock();
                lock2.unlock();
                break;
            } else {
                System.out.println("Worker2 无法获取 lock1,释放 lock2 并重试");
                lock2.unlock();
            }
            
            // 短暂休眠,避免 CPU 100%
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        LivelockExample example = new LivelockExample();
        
        Thread t1 = new Thread(example::worker1);
        Thread t2 = new Thread(example::worker2);
        
        t1.start();
        t2.start();
    }
}

以下是可能的执行顺序:

Worker1 获取 lock1
Worker2 获取 lock2
Worker1 无法获取 lock2,释放 lock1 并重试
Worker2 无法获取 lock1,释放 lock2 并重试
Worker1 获取 lock1
Worker2 获取 lock2
Worker1 无法获取 lock2,释放 lock1 并重试
Worker2 无法获取 lock1,释放 lock2 并重试
...(无限循环)

现象

3、处理方案

1. 引入随机退避

示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Random;

public class RandomBackoffSolution {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();
    private final Random random = new Random();

    public void worker1() {
        while (true) {
            lock1.lock();
            System.out.println("Worker1 获取 lock1");
            
            try {
                // 引入随机退避
                Thread.sleep(random.nextInt(100)); // 随机等待0-99ms
                
                if (lock2.tryLock()) {
                    try {
                        System.out.println("Worker1 获取 lock2,完成任务");
                        return; // 成功完成任务
                    } finally {
                        lock2.unlock();
                    }
                } else {
                    System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock1.unlock();
            }
            
            // 再次随机退避
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public void worker2() {
        while (true) {
            lock2.lock();
            System.out.println("Worker2 获取 lock2");
            
            try {
                // 引入随机退避
                Thread.sleep(random.nextInt(100)); // 随机等待0-99ms
                
                if (lock1.tryLock()) {
                    try {
                        System.out.println("Worker2 获取 lock1,完成任务");
                        return; // 成功完成任务
                    } finally {
                        lock1.unlock();
                    }
                } else {
                    System.out.println("Worker2 无法获取 lock1,释放 lock2 并重试");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock2.unlock();
            }
            
            // 再次随机退避
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        RandomBackoffSolution solution = new RandomBackoffSolution();
        
        new Thread(solution::worker1).start();
        new Thread(solution::worker2).start();
    }
}

2. 限制重试次数

示例:

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

public class RetryLimitSolution {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();
    private static final int MAX_RETRIES = 5;

    public void worker1() {
        int retryCount = 0;
        while (retryCount < MAX_RETRIES) {
            retryCount++;
            lock1.lock();
            System.out.println("Worker1 获取 lock1 (尝试 " + retryCount + "/" + MAX_RETRIES + ")");
            
            try {
                if (lock2.tryLock()) {
                    try {
                        System.out.println("Worker1 获取 lock2,完成任务");
                        return;
                    } finally {
                        lock2.unlock();
                    }
                } else {
                    System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试");
                }
            } finally {
                lock1.unlock();
            }
            
            try {
                Thread.sleep(100); // 固定等待时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Worker1 达到最大重试次数,放弃任务");
    }

    public void worker2() {
        int retryCount = 0;
        while (retryCount < MAX_RETRIES) {
            retryCount++;
            lock2.lock();
            System.out.println("Worker2 获取 lock2 (尝试 " + retryCount + "/" + MAX_RETRIES + ")");
            
            try {
                if (lock1.tryLock()) {
                    try {
                        System.out.println("Worker2 获取 lock1,完成任务");
                        return;
                    } finally {
                        lock1.unlock();
                    }
                } else {
                    System.out.println("Worker2 无法获取 lock1,释放 lock2 并重试");
                }
            } finally {
                lock2.unlock();
            }
            
            try {
                Thread.sleep(100); // 固定等待时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Worker2 达到最大重试次数,放弃任务");
    }

    public static void main(String[] args) {
        RetryLimitSolution solution = new RetryLimitSolution();
        
        new Thread(solution::worker1).start();
        new Thread(solution::worker2).start();
    }
}

3. 调整锁获取顺序

这个方案比较推荐。示例:

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

public class OrderedLockSolution {
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();

    // 定义全局锁获取顺序
    private static final Object lockOrder = new Object();

    public void worker1() {
        while (true) {
            // 按照固定顺序获取锁
            synchronized (lockOrder) {
                lock1.lock();
                System.out.println("Worker1 获取 lock1");
                
                try {
                    if (lock2.tryLock()) {
                        try {
                            System.out.println("Worker1 获取 lock2,完成任务");
                            return;
                        } finally {
                            lock2.unlock();
                        }
                    } else {
                        System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试");
                    }
                } finally {
                    lock1.unlock();
                }
            }
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public void worker2() {
        while (true) {
            // 按照相同顺序获取锁
            synchronized (lockOrder) {
                lock1.lock();
                System.out.println("Worker2 获取 lock1");
                
                try {
                    if (lock2.tryLock()) {
                        try {
                            System.out.println("Worker2 获取 lock2,完成任务");
                            return;
                        } finally {
                            lock2.unlock();
                        }
                    } else {
                        System.out.println("Worker2 无法获取 lock2,释放 lock1 并重试");
                    }
                } finally {
                    lock1.unlock();
                }
            }
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        OrderedLockSolution solution = new OrderedLockSolution();
        
        new Thread(solution::worker1).start();
        new Thread(solution::worker2).start();
    }
}

3、联系

总结

理解死锁和活锁的区别与联系,有助于开发出更健壮的并发程序。在实际开发中,应当优先考虑使用java.util.concurrent包提供的高级并发工具,而非直接使用低级的同步机制。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文