JAVA核心技术卷I(并发)
一、创建线程
Thread(Runable target)
void start():启动这个线程从而调用run()方法
void run()
二、线程状态
获取线程状态
Thread.State getState()
取值:NEW、RUNNING、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
新建线程
new Thread(r)
可运行线程
可能正在运行也可能没有运行。
阻塞和等待线程
阻塞
等待
Thread.join()
计时等待
void join(long millis)
终止线程
- run方法正常退出
- 没有捕获异常终止了run方法
三、线程属性
3.1 中断线程
java.lang.Thread
void interrupt()
向线程发送中断请求。线程的中断状态将被设置成true。如果当前该线程被一个sleep调用或阻塞,则抛出一个InterruptedException异常
static boolean interrupted()
测试当前线程(即正在执行这个指令的线程)是否被中断。这是一个静态方法。这个调用有一个副作用——它将当前线程的中断状态重置为false
boolean isInterrupted()
测试线程是否被中断。和interrupt不同,不会改变线程的中断状态
static Thread currentThread()
返回当前线程对象
3.2 守护线程
守护线程的唯一用途就是为其他线程提供服务。当只剩下守护线程的时候,虚拟机就会退出。因为如果只有守护线程,就没有必要继续运行程序了。
void setDaemon(boolean isDaemen)
标识该线程为守护线程或用户线程。方法必须在线程启动前调用。
3.3 线程名
var t = new Thread(runnable); |
这在线程转储时可能很有用。
四、同步
4.1 竞态条件
当我们运行如:
a[i] += x; |
的时候的会转为一系类的字节码:
aload_0 |
也就是说,一个语句要执行N个指令。如果在这些指令没有全部执行完之前,被中断。其他指令运行完再回头继续执行这个剩余指令,那么就有可能数据出问题。(即没有原子性)
4.2 锁对象 和 条件对象
P568
- 锁用来保护代码片段,一次只能由一个线程执行被保护的代码
- 锁可以管理试图进入被保护代码段的线程
在锁中如果需要条件判断用条件对象。
- 一个锁可以有一个或多个相关联的条件对象
- 每个条件对象管理那些已经进入被保护代码段但还不能运行的线程
可以用 synchronized
关键字代替。
4.3 synchronized 关键字
每个对象都有一个内部锁,并且这个锁有一个内部条件。这个锁会管理试图进入synchronized方法的线程,这个条件可以管理调用了wait的线程。
notifyAll => signalAll
notify => signal
wait => await
何时使用
- 最好既不用Lock/Condition 也不用 synchronized 关键字。再许多情况下,可以使用java.util.concurrent包中的某种机制,它会为你处理所有的锁定。
- 如果synchronized关键字适合你的程序,那么尽可能用这个,减少代码量。
- 如果特别需要 Lock/Condition 结构提供的格外能力,则使用Lock/Condition 。
4.4 同步块
synchronized(obj) { |
4.5 监视器概念
- 监视器是只包含私有字段的类
- 监视器类的每个对象有一个关联的锁
- 所有方法由这个锁锁定
- 锁可以有任意多个相关联的条件
4.6 volatile 和 final
P581-582
假设对共享变量除了赋值之外并不做其他操作,那么可以将这些共享变量声明为volatile
五、线程安全的集合
六、任务和线程池
6.1 Callable 与 Future
Callable< T> 是一个接口,只有一个call方法。返回类型是T。
Future保存异步计算的结果。
V get()
V get(long time, TimeUnit unit)
获取结果
boolean cancel(boolean mayInterrupt)
尝试取消这个任务的运行
boolean isCancelled()
boolean isDone()
示例
Callable<Integer> task = ...; |
6.2 执行器
执行器类(Executors)有许多静态工厂方法,用来构造线程池。
方法 | 描述 |
---|---|
newCachedThreadPool | 必要时创建新线程,空线程会保留60秒 |
newFixedThreadPool | 池中包含固定数目的线程;空闲线程会一直保留 |
newWorkStealingPool | 一种适合“fork-join”任务的线程池,其中复杂的任务会分解为简单的任务,空闲线程会“密取”较简单的任务 |
newSingleThreadPool | 只有一个线程的“池”,会顺序执行所提交的任务 |
newScheduledTahreadPool | 用于调度执行的固定线程 |
newSingleThreadScheuledExcutor | 用于调度执行的单线程“池” |
如果线程生存期很短,或者大量时间都在阻塞,那么可以用一个缓存线程池。不过,如果线程工作量很大而且不阻塞,你肯定不希望运行太多线程。
为了得到最优的运行速度,并发线程数等于处理器内核数。在这种情况下,就应当使用固定线程池,即并发线程总数有一个上限。
单线程执行器对于性能分析很有帮助。如果临时用一个单线程替换缓存或者固定线程池,就能测量不适用并发的情况下应用的运行速度会慢多少。
提交任务
- Future< T> submit(Callable< T> task)
- Future< ?> submit(Runnable task)
- Future< T> submit(Runnable task, T result)
关闭线程池
- shutdown:关闭线程池
- shutdownNow:线程池会取消所有尚未开始的任务
使用连接池的工作
- 调用Executor类的静态方法newCachedThreadPool或newFixedThreadPool
- 调用submit提交Runnable或Callable对象
- 保存好返回的Future对象,以便得到结果或取消任务
- 当不想提交任何任务时,调用shutdown