synchronized关键字用于使类或方法具有线程安全性, 这意味着只有一个线程可以拥有同步方法的锁并可以使用它, 其他线程必须等到锁释放后才能由他们中的任何一个获取。
如果我们的程序在多线程环境中运行(其中两个或多个线程同时执行),那么使用它是很重要的。但有时也会导致死锁。下面是死锁条件的一个简单示例。
文章图片
Java
//Java program to illustrate Deadlock
//in multithreading.
class Util
{
//Util class to sleep a thread
static void sleep( long millis)
{
try
{
Thread.sleep(millis);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
//This class is shared by both threads
class Shared
{
//first synchronized method
synchronized void test1(Shared s2)
{
System.out.println( "test1-begin" );
Util.sleep( 1000 );
//taking object lock of s2 enters
//into test2 method
s2.test2();
System.out.println( "test1-end" );
}
//second synchronized method
synchronized void test2()
{
System.out.println( "test2-begin" );
Util.sleep( 1000 );
//taking object lock of s1 enters
//into test1 method
System.out.println( "test2-end" );
}
}
class Thread1 extends Thread
{
private Shared s1;
private Shared s2;
//constructor to initialize fields
public Thread1(Shared s1, Shared s2)
{
this .s1 = s1;
this .s2 = s2;
}
//run method to start a thread
@Override
public void run()
{
//taking object lock of s1 enters
//into test1 method
s1.test1(s2);
}
}
class Thread2 extends Thread
{
private Shared s1;
private Shared s2;
//constructor to initialize fields
public Thread2(Shared s1, Shared s2)
{
this .s1 = s1;
this .s2 = s2;
}
//run method to start a thread
@Override
public void run()
{
//taking object lock of s2
//enters into test2 method
s2.test2(s1);
}
}
public class Deadlock
{
public static void main(String[] args)
{
//creating one object
Shared s1 = new Shared();
//creating second object
Shared s2 = new Shared();
//creating first thread and starting it
Thread1 t1 = new Thread1(s1, s2);
t1.start();
//creating second thread and starting it
Thread2 t2 = new Thread2(s1, s2);
t2.start();
//sleeping main thread
Util.sleep( 2000 );
}
}
Output : test1-begin
test2-begin
不建议使用在线IDE运行上述程序。我们可以复制源代码并在我们的本地计算机上运行它。我们可以看到它运行了不确定的时间, 因为线程处于死锁状态, 并且不允许代码执行。现在, 让我们一步一步看看那里发生了什么。
- 线程t1启动并通过获取s1的对象锁来调用test1方法。
- 线程t2启动并通过获取s2的对象锁来调用test2方法。
- t1开始打印test1-begin, t2开始打印test-2, 并且都等待1秒钟, 这样, 如果两个线程中的任何一个都没有, 则可以启动两个线程。
- t1尝试获取s2的对象锁并调用方法test2, 但是由于t2已经获取了它, 因此它等待直到它变得空闲为止。直到获得s2的锁定, 它才会释放s1的锁定。
- t2也是如此。它尝试获取s1的对象锁并调用方法test1, 但是它已被t1获取, 因此它必须等到t1释放锁。 t2也不会释放s2的锁定, 直到它获得s1的锁定。
- 现在, 两个线程都处于等待状态, 正在等待彼此释放锁。现在围绕条件争夺谁先释放锁。
- 由于它们都不准备释放锁定, 因此这是死锁状态。
- 当你运行该程序时, 看起来执行已暂停。
我们还可以通过在cmd上运行此程序来检测死锁。我们必须收集线程转储。收集命令取决于操作系统类型。如果我们使用Windows和Java 8, 则命令为jcmd $ PID Thread.print
我们可以通过运行jps命令获取PID。上面程序的线程转储如下:
jcmd 18692 Thread.print
18692:
2020-06-08 19:03:10
Full thread dump OpenJDK 64-Bit Server VM (11.0.4+10-b304.69 mixed mode, sharing):Threads class SMR info:
_java_thread_list=0x0000017f44b69f20, length=13, elements={
0x0000017f43f77000, 0x0000017f43f79800, 0x0000017f43f90000, 0x0000017f43f91000, 0x0000017f43f95000, 0x0000017f43fa5000, 0x0000017f43fb0800, 0x0000017f43f5b800, 0x0000017f44bc9000, 0x0000017f44afb000, 0x0000017f44bd7800, 0x0000017f44bd8800, 0x0000017f298c9000
}"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=57.48s tid=0x0000017f43f77000 nid=0x6050 waiting on condition[0x0000005f800ff000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@11.0.4/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@11.0.4/Reference.java:241)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@11.0.4/Reference.java:213)"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=57.48s tid=0x0000017f43f79800 nid=0x2824 in Object.wait()[0x0000005f801fe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@11.0.4/Native Method)
- waiting on(a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155)
- waiting to re-lock in wait()(a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:176)
at java.lang.ref.Finalizer$FinalizerThread.run(java.base@11.0.4/Finalizer.java:170)"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=57.47s tid=0x0000017f43f90000 nid=0x1710 runnable[0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=31.25ms elapsed=57.47s tid=0x0000017f43f91000 nid=0x4ff4 waiting on condition[0x0000000000000000]
java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 cpu=46.88ms elapsed=57.47s tid=0x0000017f43f95000 nid=0x350c waiting on condition[0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task"C1 CompilerThread0" #9 daemon prio=9 os_prio=2 cpu=93.75ms elapsed=57.47s tid=0x0000017f43fa5000 nid=0x4900 waiting on condition[0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task"Sweeper thread" #10 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=57.47s tid=0x0000017f43fb0800 nid=0x6120 runnable[0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Common-Cleaner" #11 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=57.44s tid=0x0000017f43f5b800 nid=0x5a4 in Object.wait()[0x0000005f807fe000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(java.base@11.0.4/Native Method)
- waiting on(a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(java.base@11.0.4/ReferenceQueue.java:155)
- waiting to re-lock in wait()(a java.lang.ref.ReferenceQueue$Lock)
at jdk.internal.ref.CleanerImpl.run(java.base@11.0.4/CleanerImpl.java:148)
at java.lang.Thread.run(java.base@11.0.4/Thread.java:834)
at jdk.internal.misc.InnocuousThread.run(java.base@11.0.4/InnocuousThread.java:134)"Monitor Ctrl-Break" #12 daemon prio=5 os_prio=0 cpu=15.63ms elapsed=57.36s tid=0x0000017f44bc9000 nid=0x5954 runnable[0x0000005f809fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(java.base@11.0.4/Native Method)
at java.net.SocketInputStream.socketRead(java.base@11.0.4/SocketInputStream.java:115)
at java.net.SocketInputStream.read(java.base@11.0.4/SocketInputStream.java:168)
at java.net.SocketInputStream.read(java.base@11.0.4/SocketInputStream.java:140)
at sun.nio.cs.StreamDecoder.readBytes(java.base@11.0.4/StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(java.base@11.0.4/StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(java.base@11.0.4/StreamDecoder.java:178)
- locked(a java.io.InputStreamReader)
at java.io.InputStreamReader.read(java.base@11.0.4/InputStreamReader.java:185)
at java.io.BufferedReader.fill(java.base@11.0.4/BufferedReader.java:161)
at java.io.BufferedReader.readLine(java.base@11.0.4/BufferedReader.java:326)
- locked(a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(java.base@11.0.4/BufferedReader.java:392)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)"Service Thread" #13 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=57.36s tid=0x0000017f44afb000 nid=0x6394 runnable[0x0000000000000000]
java.lang.Thread.State: RUNNABLE"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=57.35s tid=0x0000017f44bd7800 nid=0x5304 waiting for monitor entry[0x0000005f80cfe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.company.threads.Shared.test2(Deadlock.java:40)
- waiting to lock(a com.company.threads.Shared)
at com.company.threads.Shared.test1(Deadlock.java:33)
- locked(a com.company.threads.Shared)
at com.company.threads.Thread1.run(Deadlock.java:67)"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=57.35s tid=0x0000017f44bd8800 nid=0xfa4 waiting for monitor entry[0x0000005f80dfe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.company.threads.Shared.test2(Deadlock.java:40)
- waiting to lock(a com.company.threads.Shared)
at com.company.threads.Shared.test1(Deadlock.java:33)
- locked(a com.company.threads.Shared)
at com.company.threads.Thread2.run(Deadlock.java:90)"DestroyJavaVM" #16 prio=5 os_prio=0 cpu=171.88ms elapsed=55.35s tid=0x0000017f298c9000 nid=0x38ec waiting on condition[0x0000000000000000]
java.lang.Thread.State: RUNNABLE"VM Thread" os_prio=2 cpu=0.00ms elapsed=57.49s tid=0x0000017f43f73800 nid=0x52c4 runnable"GC Thread#0" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f298e1000 nid=0x47dc runnable"G1 Main Marker" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f29911000 nid=0x61c4 runnable"G1 Conc#0" os_prio=2 cpu=0.00ms elapsed=57.51s tid=0x0000017f29912000 nid=0x61c0 runnable"G1 Refine#0" os_prio=2 cpu=0.00ms elapsed=57.50s tid=0x0000017f43e0a800 nid=0x1fa8 runnable"G1 Young RemSet Sampling" os_prio=2 cpu=0.00ms elapsed=57.50s tid=0x0000017f43e0b000 nid=0x47a4 runnable
"VM Periodic Task Thread" os_prio=2 cpu=0.00ms elapsed=57.36s tid=0x0000017f44b03800 nid=0x2408 waiting on conditionJNI global refs: 15, weak refs: 0Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock monitor 0x0000017f43f87980 (object 0x000000008a2e9ce0, a com.company.threads.Shared), which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x0000017f43f87780 (object 0x000000008a2e9cd0, a com.company.threads.Shared), which is held by "Thread-0"Java stack information for the threads listed above:
===================================================
"Thread-0":
at com.company.threads.Shared.test2(Deadlock.java:40)
- waiting to lock(a com.company.threads.Shared)
at com.company.threads.Shared.test1(Deadlock.java:33)
- locked(a com.company.threads.Shared)
at com.company.threads.Thread1.run(Deadlock.java:67)
"Thread-1":
at com.company.threads.Shared.test2(Deadlock.java:40)
- waiting to lock(a com.company.threads.Shared)
at com.company.threads.Shared.test1(Deadlock.java:33)
- locked(a com.company.threads.Shared)
at com.company.threads.Thread2.run(Deadlock.java:90)Found 1 deadlock.
正如我们所看到的, 明确提到存在1个死锁。当你尝试使用计算机时, 可能会出现相同的消息。
【Java多线程中的死锁详细介绍】避免死锁状态
我们可以通过了解死锁条件来避免死锁。这是一个非常复杂的过程, 并不容易掌握。但是, 即使我们尝试, 也可以避免这种情况。有一些方法可以避免这种情况。我们无法完全消除它的可能性, 但可以减少。
- 避免嵌套锁:这是死锁的主要原因。死锁主要发生在我们将锁授予多个线程时。如果我们已经给一个线程, 则避免给多个线程锁定。
- 避免不必要的锁:我们应该只锁定那些必需的成员。不必要地锁定可能导致死锁。
- 使用线程连接:当一个线程在等待另一线程完成时, 出现死锁状态。如果发生这种情况, 我们可以在你认为执行将花费最长时间的情况下使用Thread.join。
- 如果线程正在互相等待完成, 则该条件称为死锁。
- 死锁条件是一个复杂的条件, 仅在有多个线程的情况下才会发生。
- 死锁条件可能会在运行时破坏我们的代码, 并可能破坏业务逻辑。
- 我们应尽可能避免这种情况。
推荐阅读
- Pyspark(使用Apache MLlib的线性回归)
- 使用reduce操作的金字塔形式(先升后降)连续数组
- #yyds干货盘点#k8s知识进阶知识,使用二进制安装包安装k8s的环境准备
- linux下MySQL忘记root密码#yyds干货盘点#
- DRBD+Pacemaker+NFS+KVM+K8S之drbd篇
- #yyds干货盘点#Linux网络配置故障排除命令
- MySQL启动脚本
- 模拟逻辑卷扩容
- #yyds干货盘点#解决 bash: telnet: command not found 问题