Java线程
Java线程的实现方式
线程的实现一般有三种方式:
- 使用内核线程实现。把应用的 轻量级进程 Light Weight Process(也就是线程)与系统
内核线程 Kernel Level Thread 一一对应,优点是可以把线程调度交给内核,实现上较容易,缺点是需要频繁内核调用,且线程总量有限制。这种方式也称为1:1线程模型 - 使用用户线程实现。由用户线程来实现线程的创建、销毁、调度,多个用户线程映射到一个CPU内核,这种方式的优缺点与方式一相反,实现上较复杂。这种方式也称为1:N线程模型
- 混合实现。混合上述两种方式的实现,也称为M:N线程模型
在Java中,目前使用的是1:1线程模型。
线程状态
线程有如下几个状态:
- 新建 New:线程已创建但还未开始运行的状态
- 运行 Running:线程正在运行中,也包括已就绪状态,正在等待CPU分配执行时间
- 等待 Waiting:线程无限期等待其他线程来唤醒
- 限期等待 Timed Waiting:等待其他线程唤醒,或超时后自动唤醒
- 阻塞 Blocked:线程等待锁而阻塞
- 终止 Terminated:线程运行完毕
有几种方式进入Waiting或Timed Waiting状态:
- Object.wait() 可以设置超时
- Thread.join() 可以设置超时
- LockSupport.park() 不可设置超时
- LockSupport.parkNanos() 必须设置超时
- LockSupport.parkUntil() 必须设置超时
- Thread.sleep() 必须设置超时
事实上,Thread.join()就是用Object.wait()来实现的。
LockSupport.park()与Object.wait()的区别是:wait方法必须要取得对象锁,notify只能唤醒该锁上的任意一个线程;park方法不需要锁(实际上锁的是线程本身),且可以唤醒指定线程。

线程同步机制
实现线程同步的方式通常有以下几种
synchronized关键字,通过给同步块加锁的方式来实现线程间同步。等待唤醒,通过让线程等待和唤醒的方式实现互斥同步,常见的两种方式:
- Object.wait/notify/notifyAll
- LockSupport.park/parkNanos/parkUntil/unpark
两者的异同是:notify只能随机唤醒一个线程,notifyAll能唤醒所有线程,unpark能唤醒指定线程;二者都是不可重入的。
提示
Thread.join()是基于wait(delay)来实现的
- CAS,即CompareAndSet,通过操作系统底层的CAS原子操作来保证对共享变量访问的线程安全。常见使用了CAS机制的有AtomicInteger、AtomicLong、ConcurrentHashMap等。
常见线程同步类
- ReentrantLock
ReentrantLock是支持可重入的,多条件锁,与Object.wait的使用较类似,也需要先获得锁,调用condition的await()也会阻塞并释放锁,也需要防止 虚假唤醒,因此await()应该总是被放到一个带条件判断的循环中。
对一个锁进行unlock的线程必须和lock线程是同一个,否则会报IllegalMonitorStateException。
synchronized 与 ReentrantLock的区别
synchronized在字节码层面是用monitorenter和monitorexit两条指令来实现的,支持可重入,是非公平锁。
ReentrantLock也支持可重入,默认是非公平的。还支持一些其他特性:
- 等待可中断
- 支持公平锁
- 锁可以绑定多个条件
CyclicBarrier
基于ReentrantLock实现CountDownLatch.await/countDown
基于AbstractQueuedSynchronizer实现,内部还是使用LockSupportSemaphore.acquire/release
基于AbstractQueuedSynchronizer实现,内部还是使用LockSupport
支持公平和非公平两种方式
几个类及实现机制之间的关系如下:
AQS
// TODO
查看线程状态
jstack
使用arthas thread命令