《JAVA核心技术卷I》第14章笔记
多进程与多线程本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。
sleep(long millis):
休眠给定的毫秒数。
可以通过集成Runnable接口,实现类的run方法来自定义一个线程;也可以通过构建一个Thread类的子类定义一个线程。不过不推荐集成Thread类来实现,因为JAVA中只能继承一个类。
注意:不要调用Thread类或Runnable对象的run方法。直接调用run方法,只会执行同一个线程中的任务,而不会启动新线程。应该调用Thread.start方法。这个方法将创建一个执行run方法的新线程。
Thread(Runnable target):
构造一个新线程,用于调用给定目标的run()方法。
start():
启动这个线程,将引发调用run()方法。这个方法将立即返回,并且新线程将并发运行。
中断线程
当线程的run方法执行方法体中最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将终止。
interrupt():
向线程发送中断请求。线程的中断状态将被设置为true。如果目前该线程被一个sleep调用阻塞,那么,InterruptedException异常被抛出。
interrupted():
测试当前线程(即正在执行这一命令的线程)是否被中断。注意,这是一个静态方法。这一调用会产生副作用——它将当前线程的中断状态重置为false。
isInterrupted():
测试线程是否被终止。不像静态的中断方法,这一调用不改变线程的中断状态。
currentThread():
返回代表当前执行线程的Thread对象。
线程状态
线程有6种状态:
New(新创建)
当一个线程处于新创建状态时,程序还没有开始运行线程中的代码。
Runnable(可运行)
一旦调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有运行,这取决与操作系统给线程提供运行的时间。一旦一个线程开始运行,它不必始终保持运行,可以中断当前线程给其他线程获得运行机会,线程的调度细节依赖于操作系统提供的服务。
Blocked(被阻塞)
当线程处于被阻塞或等待状态时,它暂时不活动。
当一个线程试图获取一个内部对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。只有当该线程运行持有它的时候才会变成非阻塞状态。
Waiting(等待)
当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。
Timed waiting(计时等待)
有几个方法有一个超时参数。调用它们导致线程进入计时等待状态。这一状态将一直保持到超时期满或者接受到适当的通知。
Terminated(被终止)
- 因为run方法正常退出而自然死亡
- 因为一个没有捕获的异常终止了run方法而意外死亡

join():
等待终止指定的线程
join(long millis):
等待指定的线程死亡或者经过指定的毫秒数
Thread.State getState():
得到这一线程的状态;NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING或TERMINATED之一。
线程属性
线程优先级
每一个线程有一个优先级,默认下,一个线程继承它的父线程的优先级。
当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程。但是,线程优先级是高度依赖于系统的。
setPriority(int newPriority):
设置线程的优先级。优先级必须在Thread.MIN_PRIORITY(在Thread类中定义为1)与Thread.MAX_PRIORITY(定义为10)之间。一般使用Thread.NORM_PRIORITY(定义为5)。
yield():
导致当前执行线程处于让步状态。如果有其他的可运行线程具有至少与此线程同意高的优先级,那么这些线程接下来会被调度。注意,这是个静态方法。
守护线程
守护线程的唯一用途是为其他线程提供服务,例如计时线程。当只剩下守护线程时,虚拟机就退出了,由于如果只剩下守护线程,就没有必要继续运行程序了。
注意:守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
setDaemon(boolean isDaemon):
标识该线程为守护线程或用户线程。这一方法必须在线程启动之前调用。
同步
根据各线程访问数据的次序,可能会产生讹误的对象。这样的情况称为竞争条件。
锁对象
锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码
锁可以管理试图进入被保护代码段的线程
锁可以拥有一个或多个相关的条件对象
每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程
lock():
获取这个锁;如果锁同时被另一个线程拥有则发生阻塞。
unlock():
释放这个锁。
ReentrantLock():
构建一个可以被用来保护临界区的可重入锁。
ReentrantLock(boolean fair):
构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程。但是,这一公平的保证将大大降低性能。所以默认没有是没有公平的。公平锁比常规锁要慢很多。
如果一个锁加锁后,需要其他线程给它提供条件,这时候我们就需要条件对象(也成为条件变量)。
newCondition():
返回一个与该锁相关的条件对象。
await():
将该线程放到条件的等待集中。
signalAll():
解除该条件的等待集中的所有线程的阻塞状态。
signal():
从该条件的等待集中随机地选择一个线程,解除其阻塞状态。
synchronized关键字
从1.0版本开始,Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。
即
public synchronized void method()
{
method body
}
等价于
public void method()
{
this.intrinsicLock.lock();
try
{
method body
}
finally { this.intrinsicLock.unlock(); }
}
内部锁只有一个相关条件。wait方法添加一个线程到等待集中,notifyAll/notify方法解除等待线程的阻塞状态。
将静态方法声明为synchronized也是合法的,但是没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。
内部锁和条件存在一定局限,包括:
- 不能中断一个正在试图获得锁的线程
- 试图获得锁时不能设定超时
- 每个锁仅有单一的条件,可能是不够的
notifyAll():
解除那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
notify():
随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在一个同步方法或同步块中调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
wait():
导致线程进入等待状态直到它被通知。该方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
wait(long millis)
wait(long millis,int nanos):
调至线程进入等待状态直到它被通知或者经过指定的时间。这些方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。millis:毫秒数;nanos:纳秒数,<1000000
Brian Goetz给出了“同步格言”:“如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用同步”。
那么在代码中应该使用Lock和Condition对象还是同步方法?
- 最好既不使用Lock/Condition也不使用synchronized关键字。在许多情况可以使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁。
- 如果synchronized关键字适合你的程序,那么尽量使用它,这样可以减少编写的代码数量,减少出错的几率。
- 如果特别需要Lock/Condition结构提供的独有特性,才使用Lock/Condition。
线程局部变量
共享变量有可能会导致死锁,有时可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例。
java.lang.ThreadLocal<T>
get():
得到这个线程的当前值。如果是首次调用get,会调用initialize来得到这个值。
initialize():
应覆盖这个方法来提供一个初始值。默认情况下,这个方法返回null。
set(T t):
为这个线程设置一个新值。
remove():
删除对应这个线程的值
static <S> ThreadLocal<S> withInitial(Supplier<? extends S>supplier):
创建一个线程局部变量,其初始值通过调用给定的supplier生成。
锁测试与超时
tryLock():
尝试获得锁而没有发生阻塞;如果成功返回true。这个方法会强多可用的锁,即使该锁有公平加锁策略,即便其他线程等待很久。
tryLock(long time,TimeUnit unit):
尝试获得锁,阻塞时间不会超过给定的值;如果成功返回true。
lockInterruptibly():
获得锁。但是会不确定地发生阻塞。如果线程被中断,抛出一个InterruptedException异常。
读写锁
使用读/写锁的必要步骤:
1)构造一个ReentrantReadWriteLock对象:
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
2)抽取读锁和写锁:
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
3)对所有的获取方法加读锁:
public return_type name() {
readLock.lock();
try{...}
finally { readLock.unlock(); }
}
4)对所有的修改方法加写锁:
public return_type name() {
writeLock.lock();
try{...}
finally { writeLock.unlock(); }
}
readLock():
得到一个可以被多个读操作共用的读锁,但会排斥所有写操作。
writrLock():
得到一个写锁,排斥所有其他的读操作和写操作。