Java - 同步

大家好,未来的Java巫师们!今天,我们将深入探讨Java编程中最重要的概念之一:同步。如果你是编程新手,不要担心;我会一步一步地引导你完成这次学习之旅,就像我多年来为无数学生所做的那样。所以,拿起你最喜欢的饮料,舒服地坐好,让我们一起踏上这次激动人心的冒险吧!

Java - Synchronization

什么是同步,为什么我们需要它?

想象一下,你在一个忙碌的厨房里,有多个厨师试图准备一道复杂的菜肴。如果他们都不进行任何协调就伸手去拿食材,会发生什么?混乱!这正是当多个线程尝试同时访问Java程序中的共享资源时可能发生的情况。而同步就是在这种情况下拯救我们的!

Java中的同步就像在那个忙碌的厨房里有一个交通警察,确保一次只有一个厨师(线程)可以使用特定的食材(资源)。它有助于维持秩序并防止冲突,确保我们的程序平稳运行,不会出现任何意外。

线程同步的必要性

让我们看一个现实世界的例子来理解为什么同步如此重要:

public class BankAccount {
private int balance = 1000;

public void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 正在提取...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 已提取 " + amount);
} else {
System.out.println("抱歉," + Thread.currentThread().getName() + " 余额不足");
}
}

public int getBalance() {
return balance;
}
}

在这个例子中,我们有一个简单的BankAccount类和一个withdraw方法。看起来很简单,对吧?但是当两个人同时尝试提取资金时,会发生什么呢?让我们来看看!

public class UnsynchronizedBankDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount();

Thread john = new Thread(() -> {
account.withdraw(800);
}, "John");

Thread jane = new Thread(() -> {
account.withdraw(800);
}, "Jane");

john.start();
jane.start();

try {
john.join();
jane.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("最终余额: " + account.getBalance());
}
}

当你运行这个程序时,你可能会看到这样的结果:

John 正在提取...
Jane 正在提取...
John 已提取 800
Jane 已提取 800
最终余额: -600

等等,什么?我们怎么会有负余额呢?朋友们,这就是我们所说的竞态条件。John和Jane都检查了余额,看到有足够的钱,并且提取了资金。这正是为什么我们需要同步!

在Java中实现同步

现在我们看到了问题,让我们看看Java是如何帮助我们解决它的。Java提供了几种实现同步的方法,但我们将重点介绍两种最常见的方法:

  1. 同步方法
  2. 同步块

同步方法

添加同步的最简单方法是使用方法声明中的synchronized关键字。让我们修改我们的BankAccount类:

public class BankAccount {
private int balance = 1000;

public synchronized void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 正在提取...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 已提取 " + amount);
} else {
System.out.println("抱歉," + Thread.currentThread().getName() + " 余额不足");
}
}

public int getBalance() {
return balance;
}
}

现在,当我们使用这个同步方法运行程序时,我们得到了一个更加合理的输出:

John 正在提取...
John 已提取 800
抱歉,Jane 余额不足
最终余额: 200

好多了!一次只有一个线程可以执行withdraw方法,防止了我们之前的问题。

同步块

有时,你可能不想同步整个方法。在这种情况下,你可以使用同步块。以下是做法:

public class BankAccount {
private int balance = 1000;
private Object lock = new Object(); // 这是我们的锁对象

public void withdraw(int amount) {
synchronized(lock) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 正在提取...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 已提取 " + amount);
} else {
System.out.println("抱歉," + Thread.currentThread().getName() + " 余额不足");
}
}
}

public int getBalance() {
return balance;
}
}

这实现了与同步方法相同的结果,但给了我们更多关于代码哪些部分被同步的细粒度控制。

正确同步的重要性

现在,你可能会想,“太好了!我会把一切都同步!”但是别急!过度同步可能导致性能问题。这就像在一个小镇的每个路口都设置一个交通信号灯——这样做可能很安全,但也会让一切都慢下来。

关键是只同步需要同步的部分。在我们的银行账户示例中,我们只需要同步检查和更新余额的部分。

常见的同步方法

Java提供了几种用于处理同步的有用方法。以下是 一些常见方法的表格:

方法 描述
wait() 使当前线程等待,直到另一个线程调用notify()notifyAll()
notify() 唤醒在此对象监视器上等待的单个线程
notifyAll() 唤醒所有在此对象监视器上等待的线程
join() 等待一个线程死亡

当需要更复杂的同步场景时,这些方法可能非常有用。

结论

好了,各位!我们已经穿越了Java同步的土地,从理解为什么我们需要它到在我们的代码中实现它。请记住,同步就像烹饪中的调味料——使用足够的量来增强你的程序,但不要太多,以免压倒一切。

在你继续Java的冒险之旅中,你会遇到更复杂的同步场景。但是不要害怕!有了这个基础,你完全有能力应对任何多线程挑战。

继续编码,继续学习,最重要的是,玩得开心!毕竟,编程既是艺术,也是科学。下次见,愿你的线程同步,Java强大!

Credits: Image by storyset