基本的线程机制
- 并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过多线程的机制,这些独立的任务(子任务)中的每一个都将由执行线程来驱动,一个线程就是在进程中的一个单一的顺序控制流,单个进程可以拥有多个并发执行的任务,其底层机制就是CPU的时分复用技术。
定义任务
- 要想定义任务,你可以实现
Runnable
接口,该接口有一个run()
方法,通过实现该方法可以使该任务执行你的命令,下面是一个小例子
1 | package com.muchlab.concurrence; |
Thread类
- 将
Runnable
对象转变成工作任务的传统方式是把它提交给一个Thread
构造器,然后使用Thread
对象来驱动Runnable
对象,下面是一个小例子:
1 | package com.muchlab.concurrence; |
- 多个任务的执行输出说明不同任务的执行在线程被换进换出时混在了一起,这种交换是由线程调度器自动控制的,如果你的机器有多个处理器,线程调度器或在这些处理器之间默默地分发线程。所以程序的运行结果是不确定的。
使用Executor(执行器)
- 使用
Executor
可以管理你的Thread
对象,从而简化并发编程。它的原理是Executor
在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,任务将由Executor
这个中介对象来执行。它允许你管理异步任务的执行,而无须显式地管理线程的生命周期,所以,推荐使用Executor
来执行任务。 - 执行器可以创建线程池来管理线程执行任务,下面的例子使用了
newCachedThreadPool()
方法来创建一个线程池,该线程池会为每个任务提供一个线程。
1 | package com.muchlab.concurrence; |
FixedThreadPool
- 你可以很容易地将前面示例中的
CachedThreadPool
替换为不同类型的Executor
,FixedThreadPool
使用了有限的线程集来执行所提交的任务。
1 | package com.muchlab.concurrence; |
SingleThreadPool
SingleThreadExecutor
就是线程数为1的FixedThreadPool
,每个任务都会在下一个任务开始之前运行结束,即所有任务将使用相同的线程。若提交了多个任务,那这些任务将会排队。
1 | package com.muchlab.concurrence; |
SingleThreadExecutor
对于在另一个线程中连续运行的任何事务来说,都是很有用的,比如监听进入的套接字连接任务。对于希望在线程中运行的段任务也是很有用的,比如,更新本地或者远程日志的小人物,或事件分发线程。
Callable接口:从任务中产生返回值
- 如果你希望任务在完成时能够返回一个值,则你可以实现
Callable
接口而不是Runnable
接口,它需要实现一个call()
方法,这个方法其实就是run()
方法加上返回值,并且必须使用ExecutorService.submit()
方法来调用它,下面是一个小例子:
1 | package com.muchlab.concurrence; |
休眠和优先级
休眠
- 影响任务行为的一种简单方法是调用Thread的
sleep()
方法,JavaSE5
引进了更加显式的sleep()
版本,使用TimeUnit
类,能够指定sleep()
延迟的事件单元,可以提高代码的可读性,另外,TimeUnit
还可以被用来执行转换。
1 | package com.muchlab.concurrence; |
优先级
- CPU处理现有的线程集的顺序是不确定的,但调度器会倾向于让优先权高的线程先执行。然而,这并不意味着优先权低的线程得不到执行(若一直执行优先权高的,会导致
饥饿现象
)。优先权低的线程仅仅是执行的频率比较低。 - 我们可以调用Thread对象中的
getPriority()
和setPriority()
两个方法来获取和设置线程的优先级
1 | package com.muchlab.concurrence; |
后台线程
- 所谓后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程不必属于程序。当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中所有的后台线程,main线程就是一个非后台线程
- 后台线程区别于普通线程,普通线程又可以称为用户线程,只完成用户自己想要完成的任务,不提供公共服务。而有时,我们希望编写一段程序,能够提供公共的服务,保证所有用户针对该线程的请求都能有响应。
1 | package com.muchlab.concurrence; |
继承Thread类来定义任务
- 除了实现
Runnable
接口来定义任务外,也可以继承Thread
类来定义任务,这两种方式可以说完全一致的,但一般情况下,推荐实现Runnable
接口来定义任务,因为Runnable
可以继承其他的类,而Thread
则不行。
1 | package com.muchlab.concurrence; |