写业务代码时,你可能写过这样的代码:
// 多线程并发修改共享数据privateintcount=0;publicvoidincrement(){count++;// 非原子操作,有并发问题}这就是典型的线程安全问题。多个线程同时修改同一个数据,导致结果不可预期。
理解线程和 synchronized,能帮你:
- 写出线程安全的代码
- 理解 Java 并发问题的根因
- 为学习更高级的并发工具打基础
下面我按「线程状态 → 线程创建 → synchronized 基础 → 锁升级」的顺序往下聊。
1. 线程的六种状态 🔄
1.1 线程状态概览
Java 中线程有六种状态:
// Thread.State 枚举publicenumState{NEW,// 新建RUNNABLE,// 可运行BLOCKED,// 阻塞WAITING,// 等待TIMED_WAITING,// 超时等待TERMINATED// 终止}1.2 状态转换图
线程状态转换: │ NEW → START │ RUNNABLE ←→ BLOCKED(synchronized 锁竞争) │ RUNNABLE ←→ WAITING(Object.wait()、Thread.join()、LockSupport.park()) │ RUNNABLE ←→ TIMED_WAITING(Thread.sleep()、Object.wait(long)、LockSupport.parkNanos()) │ RUNNABLE → TERMINATED1.3 各状态详解
NEW(新建)
// 创建线程,但未启动Threadthread=newThread(()->System.out.println("Hello"));// 状态:NEWRUNNABLE(可运行)
// 启动线程thread.start();// 状态:RUNNABLE(可能正在运行,也可能等待 CPU 时间片)BLOCKED(阻塞)
// 等待获取 synchronized 锁synchronized(lock){// 线程进入 BLOCKED 状态,等待获取锁}WAITING(无限期等待)
// Object.wait()synchronized(lock){lock.wait();// 释放锁,进入 WAITING}// Thread.join()thread.join();// 等待 thread 结束// LockSupport.park()LockSupport.park();// 阻塞当前线程TIMED_WAITING(超时等待)
// Thread.sleep()Thread.sleep(1000);// 1 秒// Object.wait(long)synchronized(lock){lock.wait(1000);// 最多等 1 秒// Thread.join(long)thread.join(1000);// LockSupport.parkNanos()LockSupport.parkNanos(1000000);// 1 毫秒TERMINATED(终止)
// 线程执行完毕thread.join();// 等待 thread 结束// 状态:TERMINATED2. 线程的创建与启动 🚀
2.1 继承 Thread
classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("Thread running");}}// 启动MyThreadt=newMyThread();t.start();2.2 实现 Runnable
classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Runnable running");}}// 启动Threadt=newThread(newMyRunnable());t.start();// Lambda 简化Threadt=newThread(()->System.out.println("Lambda running"));t.start();2.3 实现 Callable
classMyCallableimplementsCallable<Integer>{@OverridepublicIntegercall()throwsException{return42;}}// 启动(需要 FutureTask)FutureTask<Integer>task=newFutureTask<>(newMyCallable());newThread(task).start();Integerresult=task.get();// 获取返回值2.4 线程池
// 创建线程池ExecutorServiceexecutor=Executors.newFixedThreadPool(4);// 提交任务executor.submit(()->System.out.println("Task running"));// 关闭executor.shutdown();3. synchronized 基础 🔐
3.1 什么是 synchronized?
synchronized是 Java 的内置锁,保证同一时刻只有一个线程执行同步代码块。
// 修饰实例方法publicsynchronizedvoidincrement(){count++;}// 修饰代码块publicvoidincrement(){synchronized(this){count++;}}// 修饰静态方法publicstaticsynchronizedvoidstaticIncrement(){staticCount++;}3.2 synchronized 的特性
| 特性 | 说明 |
|---|---|
| 原子性 | 保证代码块原子执行 |
| 可见性 | 保证内存可见性 |
| 阻塞性 | 未获取锁的线程阻塞 |
| 可重入 | 同一个线程可以多次获取 |
3.3 可重入性
publicsynchronizedvoidmethodA(){System.out.println("A");methodB();// 可以重入}publicsynchronizedvoidmethodB(){System.out.println("B");}// 同一个线程可以多次获取锁3.4 synchronized 的使用场景
// 场景 1:计数器privateintcount=0;publicsynchronizedvoidincrement(){count++;}publicsynchronizedintgetCount(){returncount;}// 场景 2:单例模式publicclassSingleton{privatestaticSingletoninstance;publicstaticsynchronizedSingletongetInstance(){if(instance==null){instance=newSingleton();}returninstance;}}// 场景 3:生产者-消费者publicclassBuffer{privateQueue<String>queue=newLinkedList<>();privateintcapacity=10;publicsynchronizedvoidput(Stringitem)throwsInterruptedException{while(queue.size()>=capacity){wait();// 等待消费者消费}queue.add(item);notifyAll();// 通知消费者}publicsynchronizedStringtake()throwsInterruptedException{while(queue.isEmpty()){wait();// 等待生产者生产}Stringitem=queue.poll();notifyAll();// 通知生产者returnitem;}}4. synchronized 底层原理 🔧
4.1 Monitor(监视器锁)
synchronized底层依赖Monitor(监视器锁)实现:
Monitor 结构: │ ├─ Owner:拥有锁的线程 ├─ WaitSet:等待队列(调用 wait() 的线程) ├─ EntryList:阻塞队列(等待获取锁的线程) └─ Count:计数器(重入次数)4.2 锁的升级过程
synchronized锁会根据竞争情况自动升级:
锁升级过程: │ ├─ 无锁 │ ├─ 偏向锁(第一次获取锁) │ └─ 记录线程 ID,下次进入无需同步 │ ├─ 轻量级锁(多个线程竞争) │ └─ 自旋 CAS 获取锁 │ └─ 重量级锁(自旋失败) └─ 阻塞等待4.3 偏向锁
第一次获取锁时,使用偏向锁:
// 偏向锁:记录线程 ID// 下次同一线程进入同步块,无需任何同步操作synchronized(this){// ...}优点:无同步开销
缺点:有竞争时需要撤销
4.4 轻量级锁
多个线程交替获取锁时,使用轻量级锁:
// 轻量级锁:CAS 自旋// 线程在栈帧中创建锁记录(Lock Record)// 通过 CAS 将锁记录地址复制到对象头的 Mark Wordsynchronized(this){// ...}优点:避免线程阻塞
缺点:自旋消耗 CPU
4.5 重量级锁
多线程竞争激烈时,升级为重量级锁:
// 重量级锁:Monitor 机制// 未获取锁的线程阻塞,等待唤醒synchronized(this){// ...}优点:竞争激烈时效率高
缺点:线程阻塞,切换开销大
5. 常见问题与注意事项 ⚠️
5.1 锁对象不能为 null
// ❌ 错误Objectlock=null;synchronized(lock){// NullPointerException}// ✅ 正确Objectlock=newObject();synchronized(lock){}5.2 锁的范围要适当
// ❌ 锁范围过大publicsynchronizedvoidprocess(){// 大量无需同步的代码downloadFile();// 耗时操作parseData();saveToDb();}// ✅ 锁范围适当publicvoidprocess(){downloadFile();parseData();synchronized(this){saveToDb();// 只锁关键部分}}5.3 避免死锁
// ❌ 可能死锁publicvoidmethod1(){synchronized(lockA){synchronized(lockB){// ...}}}publicvoidmethod2(){synchronized(lockB){// 顺序相反synchronized(lockA){// ...}}}// ✅ 统一锁顺序publicvoidmethod1(){synchronized(lockA){synchronized(lockB){// ...}}}publicvoidmethod2(){synchronized(lockA){// 顺序一致synchronized(lockB){// ...}}}5.4 静态 synchronized vs 实例 synchronized
classUser{// 锁的是 User.class 对象publicstaticsynchronizedvoidstaticMethod(){// 同一时刻只有一个线程执行}// 锁的是 this(实例对象)publicsynchronizedvoidinstanceMethod(){// 同一时刻只有一个线程执行(同一实例)}}6. 常见面试题 📝
6.1 synchronized 和 Lock 的区别?
| 特性 | synchronized | Lock |
|---|---|---|
| 语法 | 关键字 | 接口 |
| 获取锁 | 自动释放 | 手动获取/释放 |
| 阻塞 | 是 | 可选择 |
| 公平锁 | 否 | 可选择 |
| 条件变量 | 无 | 有 |
6.2 synchronized 修饰 static 方法和普通方法的区别?
- static 方法:锁的是 Class 对象
- 普通方法:锁的是 this(实例对象)
6.3 什么是可重入锁?
同一个线程可以多次获取同一把锁,不会被自己阻塞。
6.4 锁升级的过程?
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
6.5 如何排查死锁?
# jstack 排查死锁jstack<pid># 输出包含 "Found 1 deadlock."小结
- 线程六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
- 线程创建:继承 Thread、实现 Runnable、实现 Callable、线程池
- synchronized:保证原子性、可见性、可重入
- 锁升级:偏向锁 → 轻量级锁 → 重量级锁
- 注意事项:锁对象不能为 null、锁范围要适当、避免死锁
下一篇(027)预告:volatile、happens-before 入门——可见性、指令重排、内存屏障、JMM 模型。