
如何避免这种Java线程死锁的悲剧如何解决Java线程死锁的问题,这个问题一直是我们持续使用中唯一需要解决的关键. 不幸的是,使用锁会导致其他问题. 让我们看一些常见的问题和相应的解决方案:
Java线程死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程正在等待根本无法释放的锁,从而导致所有工作无法完成. 假设有两条线代表两个饥饿的人多线程如何避免死锁,他们必须共享刀叉并轮流吃饭. 他们俩都需要获得两个锁: 共享刀和共享叉锁.
假设线程“ A”收到了刀,线程“ B”收到了叉子. 线程“ A”将进入阻塞状态以等待叉,而线程“ B”将阻塞以等待“ A”拥有的刀. 这只是人为设计的一个例子,但是尽管在运行时很难检测到,但是这种情况经常发生. 尽管很难检测或检查各种情况,但是只要按照以下规则设计系统,就可以避免Java线程死锁问题:
让所有线程以相同的顺序获取一组锁. 这种方法消除了X和Y所有者分别等待对方资源的问题.
将多个锁组合在一起,并将它们置于同一锁下. 在前面的Java线程死锁示例中,您可以为银器对象创建锁. 因此,必须在获得刀或叉之前获得银锁.
标记不会被变量阻塞的可用资源. 当线程获取银器对象的锁时,它可以通过检查变量来检查整个银器集合中的对象锁是否可用. 如果是,则可以获取相关的锁,否则,有必要释放银器锁,然后再试一次.
最重要的是在编写代码之前仔细设计整个系统. 多线程很难. 在开始编程之前详细设计系统可以帮助您避免发现Java线程死锁的问题.
可变变量,volatile关键字是由Java语言设计的,用于优化编译器. 以下面的代码为例:
1.classVolatileTest {
2.publicvoid foo(){
3.booleanflag = false;
4.if(标志){
5 .//这可能发生
6. }
7. }
8. }
优化的编译器可以确定该语句的if部分将永远不会执行,并且根本不会编译该部分代码. 如果该类被多个线程访问,则在由前一个线程设置该标志之后,可以通过其他线程将其重置,然后再通过if语句对其进行测试. 使用volatile关键字声明变量,可以告诉编译器,在编译时,不需要预测变量的值即可优化代码的这一部分.
无法访问的Java线程死锁有时,尽管获取对象锁没有问题,但是线程仍可能进入阻塞状态. IO是Java编程中此类问题的最佳示例. 当某个线程被对象中的IO调用阻塞时,该对象仍应可由其他线程访问. 该对象通常负责取消此阻塞的IO操作. 导致阻塞调用的线程通常会使同步任务失败. 如果该对象的其他方法也同步,则程被阻塞时,该对象等效于被冻结.
其他线程无法向该对象发送消息(例如,取消IO操作),因为该对象的Java线程无法死锁. 您必须确保不将那些阻塞调用包括在同步代码中,或确保使用同步阻塞代码的对象中存在异步方法. 尽管此方法需要注意以确保生成的代码安全运行,但它允许对象在拥有对象块的线程之后响应其他线程.
================================================ ======================
死锁是一种情况,其中多个线程同时被阻塞,并且其中一个或所有线程都在等待释放资源. 由于线程被无限期阻塞,因此程序无法正常终止.
死锁的根本原因是不恰当地使用了“ synchronized”关键字来管理对特定对象的线程访问. “ synchronized”关键字的目的是确保一次只允许一个线程执行特定的代码块. 因此,被允许执行的线程必须首先具有对变量或对象的独占访问权. 当一个线程访问一个对象时,该线程将锁定该对象,并且该锁定导致也希望访问同一对象的其他线程被阻塞,直到第一个线程释放它添加到该对象的锁为止.
因此,当使用“ synchronized”关键字时,两个线程很容易彼此等待以进行操作. 代码一是死锁的简单示例.
//代码一
Java代码
1. classDeadlocker {
2. intfield_1;
3. privateObjectlock_1 = newint [1];
4. intfield_2;
5. privateObjectlock_2 = newint [1];
6.
7. publicvoidmethod1(intvalue){
8. “已同步”(锁定_1){

9. “已同步”(锁定_2){
10. field_1 = 0; field_2 = 0;
11. }
12. }
13. }
14.
15. publicvoidmethod2(intvalue){
16. “已同步”(锁定_2){
17. “已同步”(锁定_1){
18. field_1 = 0; field_2 = 0;
19. }
20. }
21. }
22. }
23.
Java代码
1. classDeadlocker {
2. intfield_1;
3. privateObjectlock_1 = newint [1];
4. intfield_2;
5. privateObjectlock_2 = newint [1];
6.
7. publicvoidmethod1(intvalue){
8. “已同步”(锁定_1){
9. “已同步”(锁定_2){
10. field_1 = 0; field_2 = 0;
11. }
12. }
13. }
14.
15. publicvoidmethod2(intvalue){
16. “已同步”(锁定_2){
17. “已同步”(锁定_1){
18. field_1 = 0; field_2 = 0;

19. }
20. }
21. }
22. }
23.
参考代码一,请考虑以下过程:
◆一个线程(ThreadA)调用method1().
◆ThreadA在lock_1上同步,但允许抢先执行.
◆另一个线程(ThreadB)开始执行.
◆ThreadB调用method2().
◆ThreadB获取lock_2并继续执行,尝试获取lock_1. 但是ThreadB无法获得lock_1,因为ThreadA拥有lock_1.
◆ThreadB现在被阻止,因为它正在等待ThreadA释放lock_1.
◆现在轮到ThreadA继续执行了. ThreadA尝试获取lock_2,但失败,因为ThreadB已经占用lock_2.
◆ThreadA和ThreadB均被阻止,程序处于死锁状态.
当然,大多数死锁并不是那么明显,您需要仔细分析代码才能看到,特别是对于较大的多线程程序. 好的线程分析工具,例如JProbeThreadalyzer,可以分析死锁并指出导致问题的代码的位置.
隐性死锁
隐式死锁是由非标准的编程方法引起的,但不一定每次运行测试时都会导致程序死锁. 因此,在正式发布应用程序之前,可能不会发现某些隐藏的死锁,因此,它比普通的死锁更具危害性. 下面介绍导致隐式死锁的两种情况: 锁定顺序以及拥有和等待.
锁定顺序
当多个并发线程尝试同时占用两个锁时,锁顺序将发生冲突. 如果一个线程占用了另一个线程所需的锁,则可能存在死锁. 考虑以下情况,两个线程ThreadA和ThreadB需要同时具有两个锁lock_1和lock_2,其锁定过程可能如下:
◆ThreadA获得lock_1;
◆抢占ThreadA,并且VM调度程序转到ThreadB;
◆ThreadB获得lock_2;
◆抢占ThreadB,并且VM调度程序转到ThreadA;
◆ThreadA尝试获取lock_2,但是lock_2被ThreadB占用,因此ThreadA被阻止;
◆调度程序转到ThreadB;
◆ThreadB试图获取lock_1,但是lock_1被ThreadA占用,因此ThreadB被阻止;
◆ThreadA和ThreadB死锁.
必须指出的是,在不更改代码的情况下,有时不会出现上述死锁过程,并且VM调度程序可以允许其中一个线程获得两个锁lock_1和lock_2,即,线程获取两个锁过程不会中断. 在这种情况下,传统的死锁检测很难确定错误.
拥有并等待
如果一个线程获取了一个锁并等待来自另一个线程的通知,则可能会发生另一个隐藏的死锁. 考虑代码二.
//代码2
Java代码
1. publicclassqueue {
2. staticjava.lang.ObjectqueueLock _;

3. 生产者生产者_;
4. 消费者_;
5.
6. publicclassProducer {
7. voidproduce(){
8. 一会儿(!完成){
9. “已同步”(queueLock _){
10. ProduceItemAndAddItToQueue();
11. “已同步”(消费者_){
12. Consumer_.notify();
13. }
14. }
15. }
16. }
17.
18. publicclassConsumer {
19. 消耗(){
20. 一会儿(!完成){
21. “已同步”(queueLock _){
22. “已同步”(消费者_){
23. Consumer_.wait();
24. }
25. removeItemFromQueueAndProcessIt();
26. }
27. }
28. }
29. }
30. }
31. }
32.
Java代码
1. publicclassqueue {
2. staticjava.lang.ObjectqueueLock _;
3. 生产者生产者_;

4. 消费者_;
5.
6. publicclassProducer {
7. voidproduce(){
8. 一会儿(!完成){
9. “已同步”(queueLock _){
10. ProduceItemAndAddItToQueue();
11. “已同步”(消费者_){
12. Consumer_.notify();
13. }
14. }
15. }
16. }
17.
18. publicclassConsumer {
19. 消耗(){
20. 一会儿(!完成){
21. “已同步”(queueLock _){
22. “已同步”(消费者_){
23. Consumer_.wait();
24. }
25. removeItemFromQueueAndProcessIt();
26. }
27. }
28. }
29. }
30. }
31. }
32.
在代码2中多线程如何避免死锁,生产者将新内容添加到队列后通知消费者,以便其可以处理新内容. 问题在于,消费者可能将锁定保持在队列上,阻止生产者访问队列,并且即使消费者正在等待生产者的通知,消费者也将继续持有该锁定. 这样,因为生产者无法将新内容添加到队列中,但是消费者正在等待生产者添加新内容通知,从而导致死锁.
等待时持有的锁是隐式死锁. 这是因为事情可能会在理想的情况下发展—生产者线程不需要消费者持有的锁. 但是,除非有绝对可靠的理由确保Producer线程永远不需要锁,否则此编程方法仍然是不安全的. 有时“拥有并等待”可能会导致一系列线程等待. 例如,线程A持有线程B所需的锁并等待,而线程B持有线程C所需的锁并等待.
要纠正代码2中的错误,只需修改Consumer类,然后将wait()从“ synchronized”()中移出.
因此,避免死锁的一般经验法则是: 当多个线程必须访问共享资源A,B,C时,请确保每个线程以相同的顺序访问它们,例如首先访问A,访问B和B时. C.
此外,Thread类的suspend()方法也容易造成死锁,因此该方法已被放弃
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-196406-1.html
反正我从来不吃