Create by westfallon on 8/13
概念
- 一个程序同时执行多个任务。通常,每一个任务称为一个线程(thread),它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序(multithreaded)
- 多进程与多线程的本质区别在于每个进程拥有自己的一整套变量,而线程则共享数据
使用进程执行任务
- 建议的方法
- 将任务代码移到了实现了
Runnable接口类的run方法中public MyRunnable implements Runnable{ p ublic void run(){ code...; } } - 创建一个类对象
Runnable r = new MyRunnable(); - 由
Runnable创建一个Thread对象Thread t = new Thread(r); - 启动线程
t.start();
- 将任务代码移到了实现了
- 也可以通过构建一个
Thread类的子类定义一个线程class MyThread extends Thread{ public void run(){ code...; } } - 不要调用
Thread或Runnable对象的run方法。直接调用run方法,只会执行同一个线程中的任务,而不会启动新线程。应该调用Thread.start方法,这个方法将创建一个执行run方法的新线程
中断线程
-
interrupt方法可以用来请求终止线程 - 当对一个线程调用
interrupt方法时,线程的中断状态将被置位 -
interrupt方法是一个静态方法,它检测当前的线程是否被中断,而且,调用interrupt方法会清除该线程的中断状态 -
isInterrupted方法是一个实例方法,可用来检验是否有线程被中断,调用这个方法不会改变中断状态
线程状态
- 线程可以有如下六种状态
- New(新创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed waiting(计时等待)
- Terminated(被终止)
新线程创建
- 当用
new操作符创建一个新线程时,如new Thread(r),该线程还没有开始运行,这时它的状态是new
可运行线程
- 一旦调用
start方法,线程处于runnable状态 - 一旦一个线程开始运行,它不必始终保持运行
- 运行的线程被中断,目的是为了让其他线程获得运行机会
被阻塞线程和等待线程
- 当线程处于被阻塞或等待状态时,它暂不活动,它不运行任何代码且消耗最少的资源
- 以下三种情况导致线程进入非活动状态
- 当一个线程试图获取一个内部的对象锁,而该锁被其他线程执行时
- 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态
- 有几个方法有一个超时参数,调用它们导致线程进入计时等待状态
被终止的线程
- 线程因为如下两个原因之一而被终止
-
run方法正常退出而自然死亡 - 因为一个没有捕获的异常终止了
run方法而意外死亡
-
线程优先级
- 在Java中,每个线程有一个优先级,默认情况下,一个线程继承它的父线程的优先级
- 可以用
serPriority方法提高或降低任何一个线程的优先级 - 可以讲线程优先级设置在
MIN_PRIORITY(在Thread类中被定义为1)与MAX_PRIORITY(定义为10)之间的任何值 -
NORM_PRIORITY被定义为5 - 线程优先级高度依赖于系统,慎用
守护线程
- 可以通过调用
t.setDeamon(true)将线程转换为守护线程(deamon thread) - 守护线程的唯一用途是为其他线程提供服务
- 当只剩下守护线程时,虚拟机就自动退出了
- 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断
未捕获异常处理器
- 线程的
run方法不能抛出任何被检测的异常 - 在线程死亡前,异常会被传递到一个用于未捕获异常的处理器
- 可以用
setUncaughtExceptionHandler方法为任何线程安装一个处理器
锁对象
- 用
ReentrantLock保护代码块的基本结构如下:myLock.lock(); try{ code...; } finally{ myLock.unlock(); } - 要把解锁操作放在
finally字句之内。如果在临界区的代码抛出异常,锁必须释放,否则,其他线程将被永远阻塞 - 如果使用锁,就不能使用带资源的
try语句 - 锁是可重入的,因为线程可以重复地获得已经持有的锁,锁保持一个持有计数(hold count)来跟踪对
lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁,由于这一特性,被一个所保护的代码可以调用另一个使用相同的锁的方法
条件对象
- 可以使用一个条件对象来管理那些已经获得了一个锁但是不能做有用工作的进程
- 等待获得锁与
await方法的线程存在本质的区别。一旦一个线程调用await方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞,相反,它处于阻塞状态时,直到另一个线程调用统一条件上的signalAll方法时为止 - 当一个线程调用
await时,它永远没有办法重新激活自身,它寄希望于其他线程,如果没有其他线程来重新激活等待的线程,它就永远不再运行了,导致死锁 - 当一个线程拥有某个条件的锁时,它仅仅可以在该条件上调用
await、signalAll或signal方法
synchronized关键字
- 锁和条件的关键之处
- 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码
- 锁可以管理试图进入被保护代码段的线程
- 锁可以拥有一个或多个相关的条件对象
- 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程
- 如果一个方法使用
synchronized关键字,那么对象的锁将保护整个方法 - 换句话说
等价于public synchronized void method({ code...; })public void method(){ this.intrinsicLock.lock(); try{ code...; } finally{ this.intrinsicLock.unlock(); } } - 内部锁和条件存在一些局限
- 不能中断一个正在试图获得锁的线程
- 试图获得锁的线程不能设定超时
- 每个锁仅有单一的条件,可能不够
监视器
- 监视器具有以下特性
- 监视器时只包含私有域的类
- 每个监视器类的对象有一个相关的锁
- 使用该锁对所有的方法进行加锁
- 该锁可以有任意多个相关条件
Volatile类
- 多处理器的计算机能够暂存在寄存器或者本地内存缓冲区中保存内存中的值,结果会导致运行在不同处理器上的线程可能在同一个内存位置取到不同的值
- 编译器可以改变指令执行的顺序以使吞吐量最大化
-
volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域可能是被另一个线程并发更新的 - 还有一种情况可以安全的访问一个共享域,即这个域被声明为
final时
执行器
- 构建一个新的线程是有一定代价的,因为涉及到与操作系统的交互,如果程序中创建了大量的生命周期很短的线程,应该使用线程池(thread pool)
- 另一个使用线程池的原因时减少并发线程的数目
- 怎样连接线程池
- 调用
Executors类中的静态方法newCachedThreadPool或newFixedThreadPool - 调用
submit提交Runnable或Callable对象 - 如果想要取消一个任务,或如果提交
Callable对象,那就要保存好返回的Future对象 - 当不再提交任何任务时,调用
shutdown
- 调用
同步器
信号量
- 一个信号量管理许多的许可证(permits)。为了通过信号量,线程通过调用
acquire请求许可。 - 其实没有实际的许可对象,信号量仅维护了一个计数
倒计时门栓
- 一个倒计时门栓(CountDownLatch)让一个线程集等待知道计数变为0
- 倒计时门闩是一次性的,一旦计数到0,就不能再重用
障栅
-
CyclicBarrier类实现了一个集结点(rendezvous)称为障栅(barrier)。 - 如果任何一个在障栅上等待的线程离开了障栅,那么障栅就被破坏了。
- 可以提供一个可选的障栅动作(barrier action),当所有线程到达障栅的时候就会执行这一动作。
- 障栅被称为是循环的(cyclic)的,因为可以在所有等待线程被释放后被重用
交换器
- 当两个线程在同一个数据缓冲区的两个实例上工作的时候,就可以使用交换器(Exchanger)
- 典型情况是,一个线程向缓冲区填入数据,另一个线程消耗这些数据,当它们都完成后,相互交换缓冲区
同步队列
- 同步队列是一中将生产者与消费者线程配对的机制