Java编程语言提供了多种机制来同步线程,以防止并发执行时的竞态条件。其中,锁是实现线程同步的一种重要方式。本文将通过一系列示例,解释Java中的对象锁和类锁的概念,并展示如何在实际编程中应用这些锁来控制线程的执行顺序。
在Java中,对象锁和类锁是两种常见的线程同步机制。对象锁用于同步同一对象上的方法,而类锁则用于同步同一类的所有对象上的方法。理解这些锁的工作原理对于编写高效且无错误的并发程序至关重要。
以下是一个简单的Java程序,它创建了两个线程,每个线程都试图从1打印到10。程序包含两个类:"Processor"和"Locks"。"Processor"类实现了Runnable接口,其主要职责是打印1到10的数字,这是通过调用display方法实现的,该方法通过run方法代表线程执行。
public class Processor implements Runnable {
public void run() {
display();
}
public void display() {
for (int i = 0; i <= 5; i++) {
System.out.println("i = " + i + " In thread " + Thread.currentThread());
}
}
}
public class Locks {
public static void main(String[] args) {
Processor p1 = new Processor();
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p1, "t2");
t1.start();
t2.start();
}
}
上述程序编译并运行成功,但输出是交错的。当i=0时,线程"t1"在display()函数的for循环中打印i的值。一旦完成,时间片被处理到线程"t2",它在display()函数的for循环中打印i的值。然后,"t2"被交换出去,"t1"被交换进来执行。它将i的值增加到1,然后打印display()函数for循环中的i的值。一旦完成,时间片被处理到线程"t2",它在display()函数的for循环中打印i的值为1。这个过程以交错的方式继续,直到i达到5。
希望输出有序,即首先线程"t1"拥有for循环并打印所有i的值,然后"t2"拥有for循环并打印所有i的值。这时,对象锁就派上了用场。
public class Processor implements Runnable {
public void run() {
display();
}
public void display() {
synchronized (this) {
for (int i = 0; i <= 5; i++) {
System.out.println("i = " + i + " In thread " + Thread.currentThread());
}
}
}
}
public class Locks {
public static void main(String[] args) {
Processor p1 = new Processor();
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p1, "t2");
t1.start();
t2.start();
}
}
上述程序编译并运行成功,输出变得有序。首先,"t1"执行display()中的完整for循环。接下来,"t2"执行display()中的完整for循环。这是因为使用了对象锁。for循环被包裹在一个使用对象锁的块中。关键字"this"引用当前对象,在例子中是"p1"。两个线程"t1"和"t2"都是基于"p1"创建的,如下所示:
Thread t1 = new Thread(p1, "t1");
Thread t2 = new Thread(p1, "t2");
因此,当"t1"首先获得对for循环的访问权限时,它锁定了带有对象"this"的块,即"p1",并将钥匙放在口袋里。同时,即使"t1"被交换出去,"t2"也会发现门是关闭的,因为对象锁的钥匙被"t1"小心地放在口袋里。"t1"执行完整的for循环,然后解锁对象锁"p1",并将钥匙放在门口供任何人使用。"t2"被交换进来时,它拿起钥匙,进入同步块,并锁定带有对象"this"的块,即"p1",并将钥匙放在口袋里,然后继续执行for循环。