线程创建与 synchronized:最基础的并发原语
更新时间:2026-09-20。本篇是 Java 并发的地基:怎么开线程、线程有哪些状态、
synchronized到底锁的是什么、怎么用 wait/notify 协作。线上线程问题的分析见 jstack 线程栈,更细的锁见 volatile/JMM 与 JUC 锁与 AQS。
一、创建线程的几种方式
// 1) 继承 Thread(占用了单继承名额,不推荐)
class T1 extends Thread { public void run() { System.out.println("t1"); } }
// 2) 实现 Runnable(任务与线程解耦,推荐)
Runnable task = () -> System.out.println("run in " + Thread.currentThread());
new Thread(task).start();
// 3) Callable + FutureTask:有返回值、可抛异常
FutureTask<Integer> ft = new FutureTask<>(() -> 1 + 2);
new Thread(ft).start();
Integer r = ft.get(); // 阻塞等结果start() 与 run() 的区别:start() 向 OS 申请新线程并由它执行 run();直接调 run() 只是当前线程里的普通方法调用,不会并发。
线程状态(Thread.State):NEW、RUNNABLE(就绪+运行)、BLOCKED(等锁)、WAITING(无限等待)、TIMED_WAITING、TERMINATED。
二、synchronized 锁的是什么
class Counter {
private int n = 0;
private final Object lock = new Object();
public synchronized void addA() { n++; } // 实例方法:锁 this
public static synchronized void staticFn() {} // 静态方法:锁 Counter.class(类锁)
public void addB() {
synchronized (lock) { n++; } // 同步代码块:锁指定对象,粒度更细
}
}| 用法 | 锁对象 | 互斥范围 |
|---|---|---|
| 普通同步方法 | 当前实例 this | 同对象的该类同步方法互斥 |
| 静态同步方法 | 类对象 X.class | 全局该类的静态同步方法互斥 |
| 同步代码块 | 括号里的对象 | 锁同一对象的代码块互斥 |
注意:实例锁和类锁互不干扰;两个线程用不同实例调用同步方法也不互斥(锁的不是代码而是对象)。synchronized 保证原子性 + 可见性 + 有序性,并具备可重入性(同一线程可重复获得自己持有的锁)。
三、wait / notify:等待与唤醒
wait/notify/notifyAll 是 Object 的方法,必须在 synchronized 块内、且用锁对象调用:
synchronized (lock) {
while (!conditionReady) { // 必须 while 循环判断,防虚假唤醒
lock.wait(); // 释放锁并等待
}
doWork();
}
// 另一线程
synchronized (lock) {
makeReady();
lock.notifyAll(); // 唤醒所有等待者,让它们重新抢锁判断
}wait() 会释放锁(与 Thread.sleep 不释放锁不同);被唤醒后要重新竞争锁,并从 wait 处继续。生产代码优先用 notifyAll()(notify 只随机醒一个,可能唤醒的不是能继续的线程)。实际业务一般直接用 BlockingQueue/CountDownLatch 等 JUC 工具,少手写 wait。

坑
- 锁字符串常量/Integer 缓存:String 常量池、-128~127 的 Integer 可能被别处共享,锁它们会与无关代码意外互斥;用
private final Object lock。 - 用 if 判断等待条件:虚假唤醒或被 notifyAll 唤醒时条件可能仍不满足,必须 while 重判。
- 死锁:两线程以不同顺序持有两把锁并互相等待;保证所有线程按同一全局顺序加锁、设超时(
tryLock),并用 jstack 发现 Found one Java-level deadlock。 - synchronized 锁到整个方法但只有几行临界区:无谓地扩大串行范围,用同步代码块只锁共享状态。
- 在 run 里吞异常:未捕获异常会终止该线程且默认只打印栈,长驻线程要用 UncaughtExceptionHandler 或在线程池里处理。
总结
创建线程优先用 Runnable(有返回值用 Callable+FutureTask),start 才真正起线程、直接调 run 不并发;synchronized 实例方法锁 this、静态方法锁 Class、代码块锁指定对象,保证原子可见有序且可重入,实例锁与类锁互不相干;wait/notify 必须在持有锁时由锁对象调用,wait 会释放锁、条件要用 while 重判并优先 notifyAll;同时避免锁共享对象、按统一顺序加锁防死锁,复杂协作交给 JUC。 死锁现场分析见 jstack。