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教程。