Java · 并发篇
目标:
- Java线程的状态
- Java线程状态之间的转换
- 两种说法:六种/五种状态
Java线程的六种状态:

NEW 新建
不会被cpu执行
调用start之后变成RUNNABLE
通过new Thread()创建了线程,但还没有调用start()启动线程。
创建线程的方式:
new Thread(new Thread(){})继承Threadnew Thread(new Runnable(){@Override run()})实现Runnable并重写run()FutureTask<V>可以获得异步执行的结果,得到V类型的返回值- 👍线程池 负责线程的创建、维护和分配,提升性能;可以管理线程,高效调度。
RUNNABLE 可运行
Java把Ready就绪和Running执行合并为一种Runnable状态。
调用了线程的start()后处于就绪状态;线程获取到CPU时间片后开始执行run(),处于执行状态。
BLOCKED 阻塞
不会被占用CPU资源。
以下情况会让线程进入阻塞状态:
- 线程等待获取锁,该锁被另一个线程持有。
- IO阻塞:线程发起了一个阻塞式IO操作,但不具备IO操作的条件。例如:线程等待用户输入内容后继续执行。
WAITING 等待
不会被分配CPU时间片。需要被其他线程显式唤醒才会进入就绪状态。
线程调用以下方法让自己进入等待状态:
- Object.wait() 唤醒方式:notify() notifyAll()
- Thread.join() 唤醒方式:被合入的线程执行完毕
- LockSupport.park() 唤醒方式:LockSupport.unpark(Thread)
TIMED_WAITING 限时等待
不会被分配CPU时间片。若一定时间内未被唤醒,会被系统自动唤醒并进入就绪状态。
以下情况会让线程进入限时等待状态:
- Thread.sleep(time) 唤醒方式:sleep时间结束
- Object.wait(time) 唤醒方式:Object.notify() Object.notifyAll()
- LockSupport.parkNanos(time)/parkUntil(time) 唤醒方式: LockSupport.unpark(Thread) 或 线程的停止(park)时限结束
TERMINATED 终结
线程结束任务后 / 线程执行过程中发生了异常而未被处理
五种状态:依据操作系统层面来划分
- 分到了CPU时间的:运行
- 可以分到CPU时间的:就绪
- 分不到CPU时间的:阻塞
Java中的RUNNABLE涵盖了就绪、运行、阻塞I/O

线程池
private static ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 线程运行开始");
for (int j = 1; j < MAX_TURN; j++) {
System.out.println(Thread.currentThread().getName() + ", 轮次:" + j);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 线程运行结束");
});
pool.shutdown(); // 线程池不能再添加新任务。待池中任务都处理完后退出
// pool.shutdownNow(); // 立即试图停止所有执行中的线程,并不再处理阻塞队列中等待的任务,并返回那些未执行的任务
Executors工厂类
四个方法:
- new SingleThreadExecutor() 只有一个线程
- new FixedThreadPool(int nThreads) 固定大小
- new CachedThreadPool() 不限制线程数量,提交的任务都立即执行
- new SchaduledThreadPool() 定时/延时执行的任务
大厂的开发规范中禁止使用这些方法。
手动创建线程池
核心线程、任务队列、救急线程:
提交的线程放进核心线程执行,放满之后放到任务队列等待,任务队列也放满之后新的任务放到救急线程去执行;最后触发拒绝策略

七个参数
-
corePoolSize 核心线程数目
核心线程数目 -
maximumPollSize 最大线程数目
核心线程+救急线程数目 -
keepAliveTime, unit 生存时间、时间单位
针对救急线程 -
workQueue
排队任务队列 -
threadFactory 线程工厂
-
handler 拒绝策略 四种
- AbortPolicy 报异常
- CallerRunsPolicy 由调用者线程自己来执行任务
- DiscardPolicy 丢弃,不报异常不报错
- DiscardOldestPolicy 丢弃任务队列中等待最久的任务,将新任务加入队列中
提交任务
void execute(Runnable command)<T> Future<T> submit(Runnable task, T result)
sleep 和 wait
共同点: wait(), wait(long), sleep(long)效果都是让当前线程暂时放弃CPU的使用权、进入阻塞状态
不同点:
- 方法归属不同
- sleep(long) 是 Thread 的静态方法
- wait(), wait(long) 是 Object 的成员方法,每个对象都有
- 醒来时机不同
- 执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
- wait(long) 和 wait() 还可以被notify唤醒,wait()如果不唤醒就一直等下去
- 它们都可以被打断唤醒
- 锁特性不同
- wait方法的调用必须先获取wait对象的锁,而sleep无此限制
- wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃,你们还可以用)
- sleep如果在synchronized代码块中执行,不会释放对象锁(我放弃,你们也用不了)
lock和synchronized
-
语法层面
- synchronized是关键字,源码在JVM中,用C++实现
- Lock是接口,源码由JDK提供,用Java实现
- 使用synchronized时退出同步代码块锁会自动释放,而使用Lock时需要手动调用unlock方法释放锁
-
功能层面
- 都属于悲观锁,具备基本的互斥、同步、锁重入功能
- Lock提供了许多synchronized不具备的功能:获取等待状态、公平锁、可打断、可超时、多条件变量
- Lock有许多适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock;而synchronized没有
-
性能层面
- 没有竞争时,synchronized做了很多优化(如偏向锁、轻量级锁),性能挺好
- 竞争激烈时,Lock的实现通常会提供更好的性能
浙公网安备 33010602011771号