引
如果对什么是线程, 什么是进程仍存有疑惑, 请先 Google 之, 因为这两个概念不在本文的范围之内.
用多线程只有一个目的, 那就是更好的利用 CPU 的资源, 因为所有的多线程代码都可以用单线程来实现. 说这个话其实只有一半对, 因为反应 "多角色" 的程序代码, 最起码每个角色要给他一个线程吧, 否则连实际场景都无法模拟, 当然也没法说能用单线程来实现: 比如最常见的 "生产者, 消费者模型".
很多人都对其中的一些概念不够明确, 如同步, 并发等等, 让我们先建立一个数据字典, 以免产生误会.
多线程: 指的是这个程序 (一个进程) 运行时产生了不止一个线程
并行与并发:
并行: 多个 CPU 实例或者多台机器同时执行一段处理逻辑, 是真正的同时.
并发: 通过 CPU 调度算法, 让用户看上去同时执行, 实际上从 CPU 操作层面不是真正的同时. 并发往往在场景中有公用的资源, 那么针对这个公用的资源往往产生瓶颈, 我们会用 TPS 或者 QPS 来反应这个系统的处理能力.
线程安全: 经常用来描绘一段代码. 指在并发的情况之下, 该代码经过多线程使用, 线程的调度顺序不影响任何结果. 这个时候使用多线程, 我们只需要关注系统的内存, CPU 是不是够用即可. 反过来, 线程不安全就意味着线程的调度顺序会影响最终结果, 如不加事务的转账代码:
同步: Java 中的同步指的是通过人为的控制和调度, 保证共享资源的多线程访问成为线程安全, 来保证结果的准确. 如上面的代码简单加入 @synchronized 关键字. 在保证结果准确的同时, 提高性能, 才是优秀的程序. 线程安全的优先级高于性能.
好了, 让我们开始吧. 我准备分成几部分来总结涉及到多线程的内容:
扎好马步: 线程的状态
内功心法: 每个对象都有的方法(机制)
太祖长拳: 基本线程类
九阴真经: 高级多线程控制类
扎好马步: 线程的状态
先来两张图:
各种状态一目了然, 值得一提的是 "Blocked" 和 "Waiting" 这两个状态的区别:
线程在 Running 的过程中可能会遇到阻塞 (Blocked) 情况 对 Running 状态的线程加同步锁 (Synchronized) 使其进入 (lock blocked pool ), 同步锁被释放进入可运行状态(Runnable). 从 jdk 源码注释来看, blocked 指的是对 monitor 的等待(可以参考下文的图) 即该线程位于等待区.
线程在 Running 的过程中可能会遇到等待 (Waiting) 情况 线程可以主动调用 object.wait 或者 sleep, 或者 join(join 内部调用的是 sleep, 所以可看成 sleep 的一种)进入. 从 jdk 源码注释来看, waiting 是等待另一个线程完成某一个操作, 如 join 等待另一个完成执行, object.wait()等待 object.notify()方法执行.
Waiting 状态和 Blocked 状态有点费解, 我个人的理解是: Blocked 其实也是一种 wait, 等待的是 monitor, 但是和 Waiting 状态不一样, 举个例子, 有三个线程进入了同步块, 其中两个调用了 object.wait(), 进入了 waiting 状态, 这时第三个调用了 object.notifyAll(), 这时候前两个线程就一个转移到了 Runnable, 一个转移到了 Blocked.
从下文的 monitor 结构图来区别: 每个 Monitor 在某个时刻, 只能被一个线程拥有, 该线程就是 "Active Thread", 而其它线程都是 "Waiting Thread", 分别在两个队列 "Entry Set" 和 "Wait Set" 里面等候. 在 "Entry Set" 中等待的线程状态 Blocked, 从 jstack 的 dump 中来看是 "Waiting for monitor entry", 而在 "Wait Set" 中等待的线程状态是 Waiting, 表现在 jstack 的 dump 中是 "in Object.wait()".
此外, 在 runnable 状态的线程是处于被调度的线程, 此时的调度顺序是不一定的. Thread 类中的 yield 方法可以让一个 running 状态的线程转入 runnable.
内功心法: 每个对象都有的方法(机制)
synchronized, wait, notify 是任何对象都具有的同步工具. 让我们先来了解他们
他们是应用于同步问题的人工线程调度工具. 讲其本质, 首先就要明确 monitor 的概念, Java 中的每个对象都有一个监视器, 来监测并发代码的重入. 在非多线程编码时该监视器不发挥作用, 反之如果在 synchronized 范围内, 监视器发挥作用.
wait/notify 必须存在于 synchronized 块中. 并且, 这三个关键字针对的是同一个监视器(某对象的监视器). 这意味着 wait 之后, 其他线程可以进入同步块执行.
当某代码并不持有监视器的使用权时 (如图中 5 的状态, 即脱离同步块) 去 wait 或 notify, 会抛出 java.lang.IllegalMonitorStateException. 也包括在 synchronized 块中去调用另一个对象的 wait/notify, 因为不同对象的监视器不同, 同样会抛出此异常.
再讲用法:
synchronized 单独使用:
代码块: 如下, 在多线程环境下, synchronized 块中的方法获取了 lock 实例的 monitor, 如果实例相同, 那么只有一个线程能执行该块内容
直接用于方法: 相当于上面代码中用 lock 来锁定的效果, 实际获取的是 Thread1 类的 monitor. 更进一步, 如果修饰的是 static 方法, 则锁定该类所有实例.
synchronized, wait, notify 结合: 典型场景生产者消费者问题
- BlockingQueue ConcurrentHashMap
- BlockingQueue
- ArrayListBlockingQueue
- LinkedListBlockingQueue
- DelayQueue
- SynchronousQueue
- ** ConcurrentHashMap**
来源: http://www.jianshu.com/p/86cf1bd12b69