Java 多线程
synchronized关键字用于使类或方法线程安全,这意味着只有一个线程可以锁定同步方法并使用它,其他线程必须等到锁定释放并且其中任何一个获得该锁定。
如果我们的程序在多线程环境中运行,并且两个或多个线程同时执行,则使用它非常重要。但有时它也会导致一个叫做死锁的问题。
Java中死锁的介绍
操作系统中的进程使用不同的资源并以下列方式使用资源。
1)请求资源
2)使用资源
2)释放资源
死锁是一组进程被阻塞的情况,因为每个进程都持有一个资源并等待某个其他进程获取的另一个资源。
考虑一个例子,当两列火车在同一轨道上相互靠近并且只有一条轨道时,一旦它们在彼此前面,没有一列列车可以移动。当有两个或多个进程持有某些资源并等待其他资源持有的资源时,在操作系统中会出现类似的情况。例如,在下图中,进程1保持资源1并等待由进程2获取的资源2,并且进程2正在等待资源1。
如果同时遵循四个条件,则可能出现死锁(必要条件)
相互排斥:一个或多个资源是不可共享的(一次只能使用一个进程)
保持和等待:进程至少保留一个资源并等待对于资源。
No Preemption:除非进程释放资源,否则无法从进程中获取资源。
循环等待:一组进程以循环形式相互等待。
处理死锁的方法
有三种方法可以处理死锁
1)死锁防止或避免:想法是不让系统进入死锁状态。
2)死锁检测和恢复:发生死锁,然后抢先处理它一旦发生。
3)一起忽略问题:如果死锁非常罕见,那就让它发生并重启系统。这是Windows和UNIX采用的方法。
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(this);
System.out.println("test1-end");
}
// second synchronized method
synchronized void test2(Shared s1)
{
System.out.println("test2-begin");
Util.sleep(1000);
// taking object lock of s1 enters
// into test1 method
s1.test1(this);
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 GFG
{
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 begin,两者都等待1秒,这样两个线程都可以启动,如果没有。
t1尝试获取s2的对象锁并调用方法test2,但因为它已被t2获取所以它等待它变为空闲。在获得s2锁定之前,它不会释放s1的锁定。
t2也是如此。它试图获取s1的对象锁并调用方法test1但它已被t1获取,因此它必须等到t1释放锁。在获得s1锁定之前,t2也不会释放s2的锁定。
现在,两个线程都处于等待状态,等待彼此释放锁定。现在有一种竞争条件,谁将首先释放锁定。
由于他们都没有准备好释放锁定,所以这就是Dead Lock条件。
当您运行此程序时,它将看起来像执行暂停。
检测Java死锁情况
我们还可以通过在cmd上运行此程序来检测死锁。我们必须收集线程转储。要收集的命令取决于操作系统类型。如果我们使用Windows和Java 8,命令是jcmd $ PID Thread.print
我们可以通过运行jps命令来获取PID。以上程序的线程转储如下:
5524:
2017-04-21 09:57:39
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.25-b02 mixed mode):
"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x0000000002690800 nid=0xba8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #11 prio=5 os_prio=0 tid=0x0000000018bbf800 nid=0x12bc waiting for monitor entry [0x000000001937f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Shared.test1(GFG.java:15)
- waiting to lock (a Shared)
at Shared.test2(GFG.java:29)
- locked (a Shared)
at Thread2.run(GFG.java:68)
"Thread-0" #10 prio=5 os_prio=0 tid=0x0000000018bbc000 nid=0x1d8 waiting for monitor entry [0x000000001927f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Shared.test2(GFG.java:25)
- waiting to lock (a Shared)
at Shared.test1(GFG.java:19)
- locked (a Shared)
at Thread1.run(GFG.java:49)
"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001737d800 nid=0x1680 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001732b800 nid=0x17b0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x0000000017320800 nid=0x7b4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001731b000 nid=0x21b0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000017319800 nid=0x1294 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000017318000 nid=0x1efc runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000002781800 nid=0x5a0 in Object.wait() [0x000000001867f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(Unknown Source)
- locked (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(Unknown Source)
at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000000277a800 nid=0x15b4 in Object.wait() [0x000000001857f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Unknown Source)
at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)
- locked (a java.lang.ref.Reference$Lock)
"VM Thread" os_prio=2 tid=0x00000000172e6000 nid=0x1fec runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000026a6000 nid=0x21fc runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000026a7800 nid=0x2110 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000026a9000 nid=0xc54 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000026ab800 nid=0x704 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000018ba0800 nid=0x610 waiting on condition
JNI global references: 6
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000018bc1e88 (object 0x00000000d5d645a0, a Shared),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000002780e88 (object 0x00000000d5d645b0, a Shared),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at Shared.test1(GFG.java:15)
- waiting to lock (a Shared)
at Shared.test2(GFG.java:29)
- locked (a Shared)
at Thread2.run(GFG.java:68)
"Thread-0":
at Shared.test2(GFG.java:25)
- waiting to lock (a Shared)
at Shared.test1(GFG.java:19)
- locked (a Shared)
at Thread1.run(GFG.java:49)
Found 1 deadlock.
我们可以看到有明确提到发现1死锁。尝试启动计算机时可能会显示相同的消息。
避免Java死锁情况
我们可以通过了解它的可能性来避免死锁状态。这是一个非常复杂的过程,不易捕捉。但是,如果我们尝试,我们可以避免这种情况。有一些方法可以避免这种情况。我们不能完全消除它的可能性,但我们可以减少。
避免嵌套锁:这是死锁的主要原因。Dead Lock主要发生在我们为多个线程提供锁定时。如果我们已经给了多个线程,请避免锁定多个线程。
避免不必要的锁:我们应该只锁定那些必需的成员。不必要地锁定会导致死锁。
使用线程连接:当一个线程等待其他线程完成时,将显示死锁条件。如果出现这种情况,我们可以使用Thread.join,您认为执行将花费最多时间。
重点:
如果线程正在等待彼此完成,则该条件称为死锁。
死锁条件是一种复杂的情况,仅在多线程的情况下才会发生。
死锁条件可能会在运行时破坏我们的代码并可能破坏业务逻辑。
我们应该尽可能地避免这种情况。