我有两个主题。这两个线程必须调用send()
(然后receive()
)或receive()
,但这个代码有一个很好的死锁。有没有办法解决这个问题?
public class C
{
public static void main(String[] args)
{
Z z1=new Z();
Z z2=new Z();
z1.setZ(z2);
z2.setZ(z1);
z1.start();
z2.start();
}
}
class Z extends Thread
{
Z z;
Object lock=new Object();
public void setZ(Z zz)
{
z=zz;
}
public void run()
{
new Thread()
{
public void run()
{
z.send();
}
}.start();
new Thread()
{
public void run()
{
z.send();
}
}.start();
}
public void send()
{
synchronized(lock)
{
System.out.println("[Z] Send");
z.receive();
}
}
public void receive()
{
synchronized(lock)
{
System.out.println("[Z] Receive");
}
}
}
答案 0 :(得分:0)
这里的僵局非常明显。
z1
产生两个新线程;让我们称之为T1和T2。它还创建了一个名为lock
的Object实例,让我们称之为L1。
z2
也这样做;让我们调用线程T3和T4以及锁定L2。
现在,让我们假设*,T1首先开始。
现在,让我们说在第2步和第3步之间,T3开始并执行以下操作:
切换回T1,步骤3现在变为:
上下文切换到T3,L2仍由T1保持,T3等待。 上下文切换到T1,L1仍由T3保持,T1等待。
......等等。
这是解释可能发生死锁的地方。要解决此问题,您可能希望将调用移到send中synchronized块之外的z.receive(),以便在调用另一个实例的接收方法之前释放当前实例中的锁:
public void send()
{
synchronized(lock)
{
System.out.println("[Z] Send");
}
z.receive();
}
修改强>
如果锁意味着保护所有实例不会同时执行,那么您可以使用在所有线程之间共享的一个锁,因此:
class Z extends Thread
{
static final Object lock=new Object();
...
}
现在我们只有一个锁定实例,让我们称之为L0,再看看上面的步骤是如何运行的:
再次,让我们说在第2步和第3步之间,T3开始并执行以下操作:
此时T1中的步骤3不受影响。这意味着它仍然可以继续并最终释放锁定L0,这最终将让T3获得该锁定并自行继续。
不再陷入僵局。
__
*线程启动顺序从不保证与调用start()
方法的顺序相同,但在这种情况下,存在所有可能的死锁风险场景。
答案 1 :(得分:0)
最简单的方法,只需要一行更改,就是使用静态类级别锁定而不是非静态锁定。
private static Object lock=new Object();
首先,让我们看一下问题的根源: 在此示例中,发生死锁,因为两个Z实例彼此持有锁(共享资源),并且两者都在等待其他资源完成其任务。并且,没有人能够锁定它所持有的资源。
由于锁是非静态的,z的每个实例都有自己的锁,因此允许两个z实例在调用send()时一起获取实例级锁,这会导致死锁,因为调用receive()需要锁定已经锁定的对象。
这可以通过使用静态/类级别锁来解决。通过在静态锁上同步,您将同步整个类方法和属性(而不是实例方法和属性)。 换句话说,Z类的所有实例将共享该锁定对象,因此没有两个实例能够同时锁定该对象。
将锁定对象设为静态后,程序将能够按如下方式运行:
- 当z1启动可运行任务时,它将获取类级别锁定和send();
- z2尝试发送(),但z1已经保持锁定,因此它等待z1释放锁定;
- 由于z1持有Z类的锁定,因此能够接收()。
- 只有当z1完成所有工作时,z2才能开始获取Z类的锁定并继续发送和接收。
醇>
PS:如果您需要更多解释,请查看以下链接,了解有关差异的更多详细信息: Difference between static and non-static lock