基础同步
外观
< Java 编程
导航 并发编程 主题: ) |
在多线程环境中,当多个线程可以访问和修改资源时,结果可能是不可预测的。例如,让我们有一个计数器变量,它由多个线程递增。
注意!同步是一个模糊的术语。它不包括让所有线程同时执行相同的代码部分。恰恰相反。它阻止任何两个线程同时执行相同的代码部分。它同步了第一个处理的结束和第二个处理的开始。
代码部分 1.1:计数器实现
int counter = 0;
...
counter += 1;
|
上面的代码由以下子操作组成
- 读取 ; 读取变量
counter
- 添加 ; 在值上加 1
- 保存 ; 将新值保存到变量
counter
假设有两个线程需要执行该代码,如果 counter
变量的初始值为零,我们期望操作完成后值为 2。
线程 1 | 线程 2 | |||
读取 0 | 读取 0 | |||
添加 1 | 添加 1 | |||
保存 1 | 保存 1 | |||
在上面的情况下,线程 1 的操作丢失了,因为线程 2 覆盖了它的值。我们希望线程 2 等待线程 1 完成操作。请参见下文
线程 1 | 线程 2 | |||
读取 0 | 阻塞 | |||
添加 1 | 阻塞 | |||
保存 1 | 解除阻塞 | |||
读取 1 | ||||
添加 1 | ||||
保存 2 | ||||
- 临界区
- 在上面的示例中,代码
counter+=1
必须在任何给定时间仅由一个线程执行。这称为临界区。在多线程环境中编程时,我们必须识别所有属于临界区的代码部分,并确保在任何给定时间只有一个线程可以执行这些代码。这就是同步。
- 同步线程
- 线程访问临界区代码必须在线程之间同步,即确保在任何给定时间只有一个线程可以执行它。
- 对象监视器
- 每个对象都有一个对象监视器。基本上它是一个信号量,指示临界区代码是否正在被线程执行。在执行临界区之前,线程必须获得一个对象监视器。一次只有一个线程可以拥有该对象的监视器。
- 线程可以通过以下三种方式之一成为对象监视器的所有者
-
- 通过执行该对象的同步实例方法。请参见
synchronized
关键字。 - 通过执行在该对象上同步的同步语句的主体。请参见
synchronized
关键字。 - 对于 Class 类型的对象,通过执行该类的同步静态方法。
- 通过执行该对象的同步实例方法。请参见
- 对象监视器负责同步,那么为什么我们需要“wait() 和 notify() 方法”呢?
- 对于同步,我们实际上并不需要它们,但是对于某些情况来说,使用它们会很好。一个友善体贴的线程会使用它们。在执行临界区期间,线程可能会卡住,无法继续执行。这可能是因为它正在等待 I/O 和其他资源。无论如何,线程可能需要等待很长时间。对于线程来说,持有对象监视器并阻止其他线程完成工作是自私的。因此,线程通过调用对象上的
wait()
方法进入“等待”状态。它必须是线程从其获得对象监视器的相同对象。 - 另一方面,线程应该只在至少存在一个其他线程会在资源可用时调用
notify()
方法的情况下调用wait()
方法,否则线程将永远等待,除非指定了一个时间间隔作为参数。
- 让我们做一个类比。你去商店买一些东西。你在柜台排队,你得到售货员的注意 - 你得到她的“对象监视器”。你要求你要的商品。一件商品需要从仓库调入。这需要超过五分钟,所以你释放售货员(将她的“对象监视器”还给她),这样她就可以为其他顾客服务。你进入等待状态。假设还有五个其他顾客正在等待。另一个售货员从仓库调入商品。当她这样做时,她会引起第一个售货员的注意,获得她的对象监视器,并通知一个或所有等待的顾客,因此等待的顾客醒来,再次排队以引起第一个售货员的注意。
- 注意等待的顾客和从仓库调入商品的售货员之间的同步。这是一种生产者-消费者同步。
- 还要注意只有一个对象监视器,属于第一个售货员。在等待和通知发生之前,必须首先获得该对象监视器/售货员的注意。
方法final
void wait()- 当前线程必须拥有此对象的监视器。线程释放对该监视器的所有权,并等待另一个线程通过调用 notify 方法或
notifyAll
方法来通知等待此对象的监视器的线程唤醒。然后,线程等待它能够重新获得对监视器的所有权并恢复执行。
final
void wait(long time)- 与 wait 相同,但线程在指定的时间段过去后唤醒,无论是否收到通知。
final
void notify()- 此方法只能由拥有此对象的监视器的线程调用。唤醒一个正在等待此对象的监视器的线程。如果多个线程正在等待此对象的监视器,则其中一个线程会被选择唤醒。选择是任意的,并由实现决定。线程通过调用其中一个 wait 方法来等待对象的监视器。
- 唤醒的线程将无法继续执行,直到当前线程释放对该对象的锁。唤醒的线程将与任何可能正在积极竞争同步到该对象的线程以通常的方式竞争;例如,唤醒的线程在成为下一个锁定该对象的线程方面没有任何可靠的优势或劣势。
final
void notifyAll()- 与
notify()
相同,但它唤醒所有正在等待该对象的监视器的线程。
- sleep() 和 wait() 方法有什么区别?
Thread.sleep(millis)
- 这是 Thread 类的静态方法。导致当前执行的线程休眠(暂时停止执行)指定毫秒数。线程不会失去对任何监视器的所有权。这意味着如果线程拥有对象监视器,所有其他需要该监视器的线程将被阻塞。无论线程是否有监视器,都可以调用此方法。
wait()
- 此方法继承自
Object
类。线程必须首先获得该对象的监视器才能调用 wait() 方法。wait() 方法会释放对象监视器,因此它不会阻止其他等待该对象监视器的线程。