Java synchronized
上次修改时间:2024 年 7 月 16 日
在本文中,我们将展示如何使用 synchronized 关键字来确保 Java 中的线程安全。
Java 中的 synchronized 关键字是确保多线程程序中线程安全的基本工具。它提供了一种机制来控制多个线程对共享资源的访问,从而防止竞争条件和数据不一致。
我们可以通过将 synchronized 应用于方法或代码块来控制同步的范围。
当应用于方法时,它确保一次只有一个线程可以执行该方法。尝试访问 synchronized 方法的其他线程将被阻塞,直到第一个线程完成其执行并释放锁。
当应用于代码块时,它创建一个 synchronized 块。一次只有一个线程可以进入并执行块内的代码。尝试进入 synchronized 块的其他线程将被阻塞,直到第一个线程退出该块。
锁定机制
synchronized 关键字依赖于一个称为监视器(也称为固有锁)的概念。 Java 中的每个对象都有一个关联的监视器。当线程进入 synchronized 块或方法时,它会获取与该块或方法关联的对象上的锁。尝试访问相同 synchronized 块或方法的其他线程将被暂停,直到第一个线程释放该锁。这确保了只有一个线程可以独占访问共享资源。
获取和释放锁可能会引入一些开销。应该谨慎使用 synchronized 关键字,仅用于需要线程安全的关键部分。
用例
当您有多个线程并发访问和修改相同数据时,请使用 synchronized。这可以防止竞争条件,即结果取决于线程执行的不可预测的时序。它适用于多个线程需要更新共享计数器、修改数据结构或访问依赖于一致状态的关键代码段的场景。
Synchronized 方法
以下示例使用 synchronized 方法。
import java.util.Scanner;
class Task {
synchronized void process(int n) {
System.out.println(Thread.currentThread().getName());
for (int i = 1; i <= 10; i++) {
System.out.printf("%d ", n + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println();
}
}
void main() {
// only one object
final Task obj = new Task();
for (int i = 1; i < 5; i++) {
int e = i;
String name = String.format("VirThread-%d", e);
Thread.ofVirtual().name(name).start(() -> obj.process(e * 10));
}
waitForKeyPress();
}
void waitForKeyPress() {
try (var scanner = new Scanner(System.in)) {
scanner.nextLine();
}
}
该示例创建了五个虚拟线程,它们都将 Task 作为参数并调用其 process 方法。 synchronized 关键字确保一次只有一个线程启动该方法。
for (int i = 1; i < 5; i++) {
int e = i;
String name = String.format("VirThread-%d", e);
Thread.ofVirtual().name(name).start(() -> obj.process(e * 10));
}
在一个 for 循环中,我们创建并启动五个虚拟线程。
void waitForKeyPress() {
try (var scanner = new Scanner(System.in)) {
scanner.nextLine();
}
}
我们使用 scanner 来防止主线程完成。有一些工具,例如 CountDownLatch,可以完成此类任务,但为了简单起见,我们选择了这种更简单的方法。
$ java Main.java VirThread-1 11 12 13 14 15 16 17 18 19 20 VirThread-4 41 42 43 44 45 46 47 48 49 50 VirThread-3 31 32 33 34 35 36 37 38 39 40 VirThread-2 21 22 23 24 25 26 27 28 29 30
Synchronized 计数器
以下示例创建一个 synchronized 计数器。
class Counter {
private int counter = 0;
public synchronized void inc() {
counter++;
}
public synchronized void dec() {
counter--;
}
public int getCounter() {
return counter;
}
}
class Task extends Thread {
private final String name;
private final Counter counter;
public Task(Counter counter, String name) {
this.counter = counter;
this.name = name;
}
public void run() {
for (int i = 0; i <= 1000; i++) {
if (name.contains("inc"))
counter.inc();
else
counter.dec();
}
}
}
void main() {
final var counter = new Counter();
var taskInc = new Task(counter, "Thread-inc");
var taskDec = new Task(counter, "Thread-dec");
taskInc.start();
taskDec.start();
try {
taskInc.join();
taskDec.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(counter.getCounter());
}
在该示例中,我们有两个任务。一个将计数器递增 1000 次,另一个将其递减 1000 次。所以最终输出必须为零。
class Counter {
private int counter = 0;
public synchronized void inc() {
counter++;
}
public synchronized void dec() {
counter--;
}
public int getCounter() {
return counter;
}
}
为了使该示例起作用,我们必须在 inc 和 dec 方法上使用 synchronized 关键字。
try {
taskInc.join();
taskDec.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
join 方法确保主线程等待直到加入的线程完成。
$ java Main.java 0
来源
在本文中,我们使用了 synchronized Java 关键字。
作者
列出所有Java教程。