Java线程(篇外篇):线程和锁
最后更新于:2022-04-01 07:00:48
# Java线程(篇外篇):线程和锁
## 前言
本文翻译自JLS7(Java Language Specification)第17章,与大家分享。文中的英文还不知道怎么译,持续更新。
英文原文:[http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html](http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html)。
## 概述
前面章节大部分讨论的是只关心代码在同一时间执行一条语句或表达式的行为,也就是单线程执行,Java虚拟机同时可以支持多个线程执行。多个线程能够独立的执行代码,代码通常会操作共享主内存中的值和对象。多线程可以被多硬件处理器、时间分片单硬件处理器、时间分片多硬件处理器支持。
Java中线程由Thread类来代表。用户创建线程唯一的方式就是创建该类的对象;每一个线程都与这样的对象关联。当相应Thread对象的start()方法被调用时,一个线程就启动了。
线程的执行会产生不可预知的行为,尤其是在没有正确同步的情况下。本章描述了多线程程序的语义;它包含了被多线程更新的共享内存的值是否可见的规则。为了规范不同硬件架构中相似的内存模型,这些语义被称为Java程序语言内存模型。当没有争论出现时,我们将这些规则简称为"内存模型"。
这些语义没有规定多线程程序如何被执行。相反,它们描述了多线程程序能够展现出的行为。产生唯一允许行为的任何一个执行策略都是一个可接受的执行策略。
## 同步(Synchronization)
Java程序语言提供了多种机制用于线程通信。这些方法中最基本的就是同步,同步使用监视器实现。Java中每个对象都与一个监视器关联,线程基于某个对象来实现持有锁或者释放锁。同一时间只有一个线程可以持有监视器上的锁。此时,其它线程在尝试持有该监视器上的锁时,都将被阻塞,直到原来的线程释放该监视器上的锁。一个线程t也许会锁定特定的监视器多次; 每次解锁反转一个锁定操作的效果。
synchronized语句([§14.19](http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.19))使用一个对象的引用作为监视器,在执行其代码块之前,会先尝试对该监视器对象进行锁定操作。锁定操作成功完成后,会进一步执行代码块。如果代码块执行完,包括正常的或者异常的,那么该监视器上的锁会自动的释放。
synchronized方法([§8.4.3.6](http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.3.6 "8.4.3.6. synchronized Methods"))被调用时,会自动进行锁定操作;直到锁定操作成功的完成,方法体才被执行。如果该方法是一个非静态方法,监视器对象是该方法所在类的一个实例。如果该方法是一个静态方法,监视器对象是该方法所在类的Class对象。如果方法体执行完,包括正常的或者异常的,那么该监视器上的锁会自动的释放。
Java程序语言既不阻止也不需要检测死锁条件。当多线程程序直接或间接的持有多个对象上的锁时应该使用常规的技术来避免死锁问题,如果必要的话,创建更高级别的不会死锁的锁定原语。
还有其它的机制,如volatile变量的读写和使用java.util.concurrent包中的类,提供了同步的替代方法。
## 等待集合和通知(Wait Sets and Notification)
每一个对象,除了有关联的监视器,还有一个关联的等待集合。等待集合中存储的是线程。
当一个对象被创建时,它的等待集合是空的。等待集合中线程的增加和删除这两个基本操作是原子的。对等待集合的操作只有通过Object.wait、Object.notify、Object.notifyAll三个方法。
等待集合操作也可以被线程的中断状态、线程类处理中断的方法所影响。另外,线程类的睡眠和合并方法拥有那些等待和通知操作的衍生属性。
## 等待(Wait)
wait()或带时间参数的wait(long millisecs)和wait(long millisecs, int nanosecs)被调用时会触发等待操作。
*调用wait(0)或wait(0, 0)是和wait()等价的。*
一个线程从等待中正常返回如果它没有抛出InterruptedException。
设线程t为执行m对象wait方法的线程,and let n be the number of lock actions by t on m that have not been matched by unlock actions。则会发生下面这些情况之一:
* 如果n=0(也就是线程t已经不再持有对象m的锁),将会抛出IllegalMonitorStateException。
* 如果是带时间参数的wait方法,并且nanosecs参数不在0-999999范围内或者millisecs是负数,将会抛出IllegalArgumentException。
* 如果线程t被中断,将会抛出InterruptedException并且线程t的中断状态设为false。
* 否则,以下情况会顺序发生:
1. 线程t被添加到m对象的等待集合中,并释放m上的锁。
2. 线程t直到从对象m的等待集合中移除后,才会执行后面的代码。直到下列的情况发生任意一种时,线程才会从等待队列中移除,并重新进行线程调度:
* 其他某个线程调用对象m的 notify 方法,并且线程t碰巧被任选为被唤醒的线程。
* 其他某个线程调用对象m的 notifyAll 方法。
* 其他某个线程中断了线程t。
* 如果是带时间参数的wait方法,从wait操作时间开始,millisecs毫秒数+nanosecs纳秒数指定的超时时间已用完。
* 虚假唤醒(spurious wake-ups)。*为了防止虚假唤醒,wait操作必须放在循环中。*
每个线程对于能引起它从等待集合中移除的事件都必须有一个确定的执行顺序。该顺序不必与其它的排序相一致,但是该线程必须表现得好像这些事件是按照这个顺序发生的。
例如,假设一个线程t在对象m的等待集合中,线程t的中断和对象m的通知两个事件同时发生,那么这两个事件必须有一个执行顺序。如果中断事件先执行,线程t会抛出一个InterruptedException并从等待中退出,并且对象m的等待集合中的其它线程会接收到通知。如果通知事件先执行,线程t从等待中正常退出,中断事件被挂起。
6. 线程t在m上执行锁操作。
7. 如果线程t是在第2部的时候从m的等待集合中移除,t的中断状态设置为false并且wait方法抛出InterruptedException。
## 通知(Notification)
notify和notifyAll被调用时会触发通知操作。
设线程t为执行m对象通知方法的线程,and let n be the number of lock actions by t on m that have not been matched by unlock actions。则会发生下面这些情况之一:
* 如果n=0,将会抛出IllegalMonitorStateException。这是线程t已经不再持有对象m的锁的情况。
* 如果n>0并且这是一个notify操作,如果m的等待集合非空,m的等待集合中一个线程u被选中,从等待集合中移除。等待集合中哪一个线程被选中是没有规则的。从等待集合中移除使u从等待状态恢复。然而,在u恢复后,直到线程t释放了在监视器的所之后,u才能持有锁。
* 如果n>0并且这是一个notifyAll操作,所有的线程都会从m的等待集合中移除,重新开始竞争。然而,恢复后,它们之中只有一个会持有监视器的锁。
## 中断(Interruptions)
Thread.interrupt、ThreadGroup.interrupt被调用时会触发中断操作。
设t为执行u.interrupt的线程,该操作会将线程u的中断状态设为true。
另外,如果m的等待集合中包含u,u会被移出。This enables u to resume in a wait action, in which case this wait will, after re-locking m's monitor, throw InterruptedException。
调用Thread.isInterrupted能确定一个线程的中断状态。静态方法Thread.interrupted也许会被一个守护线程调用,并清除它自己的中断状态。
## 等待、通知、中断的相互影响(Interactions of Waits, Notification, and Interruption)
待译。
## 睡眠和让步(Sleep and Yield)
待译。