简体   繁体   English

java中防止死锁的技巧

[英]Tips to prevent deadlocks in java

I am studying java threads and deadlocks, I understand deadlock's examples but I wonder if there are general rules to follow to prevent it.我正在研究 java 线程和死锁,我理解死锁的例子,但我想知道是否有一般规则可以防止它。

My question is if there are rules or tips that can be applied to the source code in java to prevent deadlocks?我的问题是是否有规则或技巧可以应用于java中的源代码以防止死锁? If yes, could you explain how to implement it?如果是,您能解释一下如何实施吗?

Some quick tips out of my head我脑子里的一些快速提示

  • don't use multiple threads (like Swing does, for example, by mandating that everything is done in the EDT)不要使用多线程(就像 Swing 那样,例如,通过强制要求一切都在 EDT 中完成)
  • don't hold several locks at once.不要同时持有多个锁。 If you do, always acquire the locks in the same order如果这样做,请始终以相同的顺序获取锁
  • don't execute foreign code while holding a lock持有锁时不要执行外部代码
  • use interruptible locks使用可中断锁

Encapsulate, encapsulate, encapsulate!封装,封装,封装! Probably the most dangerous mistake you can make with locks is exposing your lock to the world (making it public).使用锁可能犯的最危险的错误是将你的锁暴露给全世界(公开)。 There is no telling what can happen if you do this as anyone would be able to acquire the lock without the object knowing (this is also why you shouldn't lock this ).不知道如果你这样做会发生什么,因为任何人都可以在对象不知道的情况下获得锁(这也是你不应该锁定this )。 If you keep your lock private then you have complete control and this makes it more manageable.如果您将锁保密,那么您就拥有完全的控制权,这使得它更易于管理。

  1. Avoid locks by using lock-free data structures (eg use a ConcurrentLinkedQueue instead of a synchronized ArrayList )通过使用无锁数据结构来避免锁(例如使用ConcurrentLinkedQueue而不是同步的ArrayList
  2. Always acquire the locks in the same order, eg assign a unique numerical value to each lock and acquire the locks with lower numerical value before acquiring the locks with higher numerical value始终按照相同的顺序获取锁,例如为每个锁分配一个唯一的数值,在获取数值较大的锁之前先获取数值较小的锁
  3. Release your locks after a timeout period (technically this doesn't prevent deadlocks, it just helps to resolve them after they've occurred)在超时后释放你的锁(从技术上讲,这并不能防止死锁,它只是在它们发生后帮助解决它们)

Read and understand Java: Concurrency and Practice.阅读并理解Java:并发与实践。 This isn't about "tips" to avoid deadlock.这不是关于避免死锁的“技巧”。 I would never hire a developer who knew a few tips to avoid deadlock and often avoided deadlock.我永远不会聘请知道一些避免死锁的技巧并且经常避免死锁的开发人员。 It's about understanding concurrency.这是关于理解并发性。 Fortunately there is a comprehensive intermediate-level book on the topic, so go read it.幸运的是,有一本关于该主题的综合性中级书籍,因此请阅读它。

  1. Don't use locks.不要使用锁。
  2. If you must, keep your locks local.如果必须,请将锁放在本地。 Global locks can be really tricky.全局锁可能非常棘手。
  3. Do as little as possible when you hold the lock.握住锁时尽量少做。
  4. Use stripes to only lock segments of your data使用条带仅锁定数据段
  5. Prefer Immutable types.首选不可变类型。 Many times this means copying data instead of sharing data.很多时候这意味着复制数据而不是共享数据。
  6. Use compare and set (CAS) mechanics instead, See AtomicReference for example.改用比较和设置 (CAS) 机制,例如参见AtomicReference

There is pretty much just one big rule when it comes to preventing deadlocks:在防止死锁方面,几乎只有一个大规则:

If you need to have multiple locks in your code, make sure everyone always acquire them in the same order.如果您的代码中需要有多个锁,请确保每个人始终以相同的顺序获取它们。

Keeping your code free from locks should pretty much always be your goal though.不过,让您的代码免于锁定应该几乎始终是您的目标。 You can try to get rid of them by using immutable or thread-local objects and lock-free data structures.您可以尝试通过使用不可变或线程本地对象和无锁数据结构来摆脱它们。

Given a design choice, use message-passing where there only locks are in the queue push/pop.给定一个设计选择,在队列推送/弹出中只有锁的地方使用消息传递。 This is not always possible but, if it is, you will have very few deadlocks.这并不总是可能的,但如果是这样,您将很少有死锁。 You can still get them, but you have to try really hard :)你仍然可以得到它们,但你必须非常努力:)

Boolean flag example布尔标志示例

I love this example.我喜欢这个例子。 It starts two threads that share a boolean flag:它启动两个共享布尔标志的线程:

public class UntilYouUpdateIt 
{
    public static boolean flag = true;

    public static void main(String[] args) throws InterruptedException 
    {
        Thread t1 = new Thread(()->
        {
            while(flag){}
            System.out.println("end");
        });
        t1.start();

        Thread.sleep(100);

        Thread t2 = new Thread(()->
        {
           flag = false;
           System.out.println("changed");
        });
        t2.start();
      }
}

First thread will loop until flag is false, which happens in the first line of the 2nd thread.第一个线程将循环直到flag为 false,这发生在第二个线程的第一行。 The program won't ever finish, and it's ouput would be:该程序永远不会完成,它的输出将是:

changed

2nd thread dies, meanwhile the 1st one will loop forever .第二个线程死亡,同时第一个线程将永远循环。

Why is it happenning?为什么会发生? Compiler opmitizations . Compiler opmitizations Thread1 will never check again flag's value, as: Thread1 永远不会再次检查标志的值,如:

  • the operation inside the loop is cheap (nothing to do)循环内部的操作很便宜(无事可做)
  • The compiler knows there's no other entity that can modify the flag value (as the first thread doesn't, and the 2nd one is already dead).编译器知道没有其他实体可以修改标志值(因为第一个线程没有,而第二个线程已经死了)。 So it assumes flag will always be true .所以它假设 flag 永远是 true

In other words, Thread1 will always be reading the flag value from the cache , where it is set as true .换句话说, Thread1 将始终从cache读取flag值,它被设置为true


Two ways to solve/test this:解决/测试此问题的两种方法:

    Thread t1 = new Thread(()->
    {
        while(flag)
        {
           System.out.print("I'm loopinnnng");
        }
        System.out.println("end");
    });

If some "heavy" operation is included ( int i=1 or similar would'nt work neither), such as a System call, the optimizer will be a little more careful , checking the flag boolean in order to know if he's not wasting resources.如果包含一些“重”操作( int i=1或类似的操作都不起作用),例如System调用,优化器会更加小心,检查flag布尔值以了解他是否没有浪费资源. The output would be:输出将是:

I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
(....)
changed
end

or或者

I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
I'm loopinnnng
(....)
end
changed

Depending which thread was assigned the cpu time at the end.取决于最后为哪个线程分配了 CPU 时间。

The correct solution to avoid these kind of deadlocks, when working with boolean variables, should be including the volatile keyword.在使用布尔变量时,避免此类死锁的正确解决方案应该包括volatile关键字。

volatile tells the compiler: do not try to optimize when this variable is involved. volatile告诉编译器:当涉及到这个变量时不要尝试优化。

So, this same code with just that keyword added:因此,仅添加了该关键字的相同代码:

public class UntilYouUpdateIt 
{
    public static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException 
    {
        Thread t1 = new Thread(()->
        {
            while(flag){}
            System.out.println("end");
        });
        t1.start();

        Thread.sleep(100);

        Thread t2 = new Thread(()->
        {
           flag = false;
           System.out.println("changed");
        });
        t2.start();
      }
}

Will output:将输出:

changed
end

or或者

end
changed

The result is both threads finishing correctly, avoiding any deadlock.结果是两个线程都正确完成,避免了任何死锁。


Unordered locks example无序锁示例

This one is a basic one:这是一个基本的:

public void  methodA() 
{
  //...

  synchronized(lockA)
  {
     //...
 
     synchronized(lockB)
     {
      //...
     }
   }
} 


public void methodB() 
{
  //...

  synchronized(lockB)
  {
     //...
  
    synchronized(lockA)
     {
      //...
     }
   }
}

This methods would probably create a great deadlock if called by many threads.如果被多个线程调用,此方法可能会造成很大的死锁。 This is because the objects are locked in different order.这是因为对象以不同的顺序锁定。 This is one of the most common reasons of deadlocks, so if you want to avoid them, be sure that the locks are aquired in order.这是死锁最常见的原因之一,因此如果您想避免它们,请确保按顺序获取锁。

Deadlock in java is a programming situation where two or more threads are blocked forever. Java 中的死锁是两个或多个线程被永远阻塞的编程情况。 Java deadlock situation arises with at least two threads and two or more resources. Java 死锁情况出现在至少两个线程和两个或更多资源的情况下。

How to Detect Deadlock in Java如何检测 Java 中的死锁

To detect a deadlock in Java, we need to look at the java thread dump of the application, we can generate thread dump using VisualVM profiler or using jstack utility.要检测 Java 中的死锁,我们需要查看应用程序的Java 线程转储,我们可以使用VisualVM分析器或使用jstack实用程序生成线程转储。

For analyzing deadlock, we need to look out for the threads with the state as BLOCKED and then the resources it's waiting to lock.为了分析死锁,我们需要注意状态为BLOCKED的线程,然后是等待锁定的资源。 Every resource has a unique ID using which we can find which thread is already holding the lock on the object.每个资源都有一个唯一的 ID,使用它我们可以找到哪个线程已经持有对象的锁。

How to avoid deadlock in java java中如何避免死锁

These are some of the guidelines using which we can avoid most of the deadlock situations.这些是我们可以避免大多数死锁情况的一些指导方针。

  • Avoid deadlock by breaking circular wait condition : In order to do that, you can make arrangement in the code to impose the ordering on acquisition and release of locks.通过打破循环等待条件避免死锁:为了做到这一点,您可以在代码中进行安排,以强加获取和释放锁的顺序。 If lock will be acquired in a consistent order and released in just opposite order, there would not be a situation where one thread is holding a lock which is acquired by other and vice-versa.如果以一致的顺序获取锁并以相反的顺序释放锁,则不会出现一个线程持有另一个线程获取的锁的情况,反之亦然。
  • Avoid Nested Locks: This is the most common reason for deadlocks, avoid locking another resource if you already hold one.避免嵌套锁:这是死锁的最常见原因,如果您已经持有另一个资源,请避免锁定另一个资源。 It's almost impossible to get a deadlock situation if you are working with only one object lock.如果您只使用一个对象锁,几乎不可能出现死锁情况。
  • Lock Only What is Required: You should acquire lock only on the resources you have to work on, if we are only interested in one of its fields, then we should lock only that specific field not complete object . Lock Only What is required:你应该只锁定你必须处理的资源,如果我们只对它的一个字段感兴趣,那么我们应该只锁定那个特定的字段而不是完整的对象
  • Avoid waiting indefinitely: You can get a deadlock if two threads are waiting for each other to finish indefinitely using thread join.避免无限期等待:如果两个线程使用线程连接无限期地等待对方完成,则可能会出现死锁。 If your thread has to wait for another thread to finish, it's always best to use join with maximum time you want to wait for the thread to finish .如果您的线程必须等待另一个线程完成,则最好使用 join with maximum time you want to wait for the thread to finish
  1. Avoid nested locks.避免嵌套锁。 This is the most common reason for deadlock.这是死锁最常见的原因。 Avoid locking another resource if you already hold one.It is almost impossible to get deadlock if you are working with only one object lock.如果您已经持有一个资源,请避免锁定另一个资源。如果您只使用一个对象锁,几乎不可能发生死锁。

  2. Lock only what is required.只锁定需要的东西。 Like lock particular field of object instead of locking whole object if it serves your purpose.如果它符合您的目的,就像锁定对象的特定字段而不是锁定整个对象。

  3. Do not wait indefinitely.不要无限期地等待。

  1. Unless required, do not share data over multiple threads.除非需要,否则不要通过多个线程共享数据。 If data can't be changed after creation/initialization, stick to final variables.如果在创建/初始化后无法更改数据,请坚持使用最终变量。
  2. If you can't avoid shared data among multiple threads, use granular synchronized blocks or Lock s.如果无法避免多个线程之间共享数据,请使用粒度synchronized块或Lock
  3. If you are using only synchronized code blocks, make sure that locks are acquired/released in a certain order.如果您只使用synchronized代码块,请确保按特定顺序获取/释放锁。
  4. Look out for other alternatives : volatile or AtomicXXX variables or Lock API寻找其他替代方案:volatile 或AtomicXXX变量或Lock API

Related SE questions:相关 SE 问题:

Avoid synchronized(this) in Java? 在 Java 中避免同步(这个)?

Difference between volatile and synchronized in Java Java中volatile和synchronized的区别

Volatile boolean vs AtomicBoolean 挥发性布尔值与原子布尔值

  • Avoid Nested Locks避免嵌套锁
  • Avoid Unnecessary Locks避免不必要的锁
  • Use thread join()使用线程连接()

Unfortunately, there are data models with circular data flow (A->B->C->A) across multiple threads.不幸的是,存在具有跨多个线程的循环数据流 (A->B->C->A) 的数据模型。 All said above doesn't work for it.上面说的都行不通。 From what might help, a data transfer method between threads could use a lockless buffered input like java concurrency collections.从可能的帮助来看,线程之间的数据传输方法可以使用无锁缓冲输入,如 Java 并发集合。 And data chunks itself are implemented as stateless immutable objects.数据块本身被实现为无状态的不可变对象。 A GUI systems highly likely use this approach. GUI 系统很可能使用这种方法。 This way a "lockfull" wait-notify scheme appears not needed, however thread concurrency control still exists but it is hidden inside collections and this way is local.这种方式似乎不需要“锁定”等待通知方案,但是线程并发控制仍然存在,但它隐藏在集合中,并且这种方式是本地的。 The problem really is hardly avoidable.这个问题真的很难避免。 The roots originate from zero latency assumed by math model.根源源于数学模型假设的零延迟。 But in real system every action takes time, so it creates latency and finally leads to deadlocks.但在实际系统中,每个动作都需要时间,因此会产生延迟并最终导致死锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM