在Java中,多线程编程是一个复杂但必不可少的话题。线程同步是确保线程安全的关键手段之一。在多线程环境中,一个线程可能需要同时持有多个锁,以确保对共享资源的访问是安全的。本文将通过一个示例程序来展示Java线程如何管理多个锁,并讨论这种做法的优缺点。
以下是一个Java程序,演示了线程如何同时获取多个锁。
public class Processor implements Runnable {
Object objLock;
String name;
public Processor(String name, Object objLock) {
this.objLock = objLock;
this.name = name;
}
public void run() {
display();
}
public void display() {
synchronized(this) {
synchronized(objLock) {
for (int i = 0; i <= 5; i++) {
System.out.println("i = " + i + " In thread " + Thread.currentThread() +
" acquired lock on this i.e. " + name +
" and objLock");
}
}
}
}
}
public class Locks {
public static void main(String[] args) {
Object objLock = new Object();
Processor p1 = new Processor("p1", objLock);
Processor p2 = new Processor("p2", objLock);
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p1, "t2");
Thread t3 = new Thread(p2, "t3");
Thread t4 = new Thread(p2, "t4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
程序运行后,输出如下:
i = 0 In threadThread[t1, 5, main]acquired lock on this i.e. p1 and objLock
i = 1 In threadThread[t1, 5, main]acquired lock on this i.e. p1 and objLock
i = 2 In threadThread[t1, 5, main]acquired lock on this i.e. p1 and objLock
i = 3 In threadThread[t1, 5, main]acquired lock on this i.e. p1 and objLock
i = 4 In threadThread[t1, 5, main]acquired lock on this i.e. p1 and objLock
i = 5 In threadThread[t1, 5, main]acquired lock on this i.e. p1 and objLock
i = 0 In threadThread[t3, 5, main]acquired lock on this i.e. p2 and objLock
i = 1 In threadThread[t3, 5, main]acquired lock on this i.e. p2 and objLock
i = 2 In threadThread[t3, 5, main]acquired lock on this i.e. p2 and objLock
i = 3 In threadThread[t3, 5, main]acquired lock on this i.e. p2 and objLock
i = 4 In threadThread[t3, 5, main]acquired lock on this i.e. p2 and objLock
i = 5 In threadThread[t3, 5, main]acquired lock on this i.e. p2 and objLock
从输出中可以看到,每个线程在执行时都成功地获取了两个锁:一个是当前对象的锁(即p1或p2),另一个是所有线程共享的objLock。
在主函数main()中,创建了两个Processor对象p1和p2,并将它们分别传递给两个线程t1和t2,以及t3和t4。这意味着objLock可以被所有线程看到。
在run()方法中,display()函数包含一个for循环,该循环在"this"和"objLock"上同步。因此,任何线程必须先获取自身的锁,然后才能获取objLock。
当所有线程启动时,t1和t2会竞争获取p1的锁,而t3和t4会竞争获取p2的锁。假设t1获取了p1的锁,t3获取了p2的锁,那么t1和t3将竞争objLock。如果t1获得了objLock,它将执行for循环,同时持有两个锁。完成后,t1将释放两个锁,t3将有机会执行for循环。t2和t4之间会发生类似的竞争条件,得到了一个清晰的有序输出。
这个示例清楚地展示了一个线程可以获取并持有多个锁。但这是否是一个好的做法呢?将在后面讨论。
获取多个锁可以确保线程在执行关键代码段时,对共享资源的访问是安全的。然而,这种做法也可能导致死锁,因为线程可能在等待获取一个锁的同时持有另一个锁。为了避免这种情况,通常建议使用锁排序策略,即总是以相同的顺序获取锁。