返回专题首页

Java 专题

并发编程入门:线程、线程池、锁与并发容器

Java 并发很容易给人一种“术语爆炸”的感觉:线程、线程池、锁、`volatile`、CAS、AQS、并发容器、`CompletableFuture`,每一项都像一整套体系。很多人一看到这些词就会本能紧张,觉得并发是不是必须一口气学成底层专家才能碰。

Java 专题第 09 篇 / 26 篇6 分钟

Java 并发很容易给人一种“术语爆炸”的感觉:线程、线程池、锁、volatile、CAS、AQS、并发容器、CompletableFuture,每一项都像一整套体系。很多人一看到这些词就会本能紧张,觉得并发是不是必须一口气学成底层专家才能碰。

其实并发入门阶段最重要的,不是一次把所有机制吃透,而是先把“为什么会发生并发问题”这件事看清楚。因为并发真正的难点从来不是 API 数量,而是:

  • 多个任务如何共同使用资源;
  • 共享状态为什么会带来风险;
  • 为什么有些代码看起来对,运行起来却会偶发出错;
  • 为什么线程越多不一定越快。

只要这层心智先立住,后面的工具和底层细节才有位置可放。

并发最先要看的,不是线程,而是资源竞争

很多人一学并发,就先问“怎么开线程”。但在工程里,更值得先问的是:

  • 现在到底是谁在和谁竞争资源?
  • 这个场景是 CPU 紧张,还是 I/O 等待?
  • 问题是共享状态冲突,还是任务调度拥堵?
  • 系统更缺吞吐,还是更缺稳定性?

程序只要开始并发执行,最常见的几类问题就会出现:

  • 多个任务同时读写同一份数据;
  • 有限资源被大量任务争抢,比如线程、连接、CPU 时间;
  • 某些任务需要等待其他任务先完成;
  • 吞吐、响应时间和稳定性之间需要平衡。

所以并发编程本质上是资源治理问题,其次才是语法和工具问题。

线程到底是什么?

线程可以先理解为程序中的执行单元。你可以把它想成:同一个进程里,有多条执行路径同时往前推进。问题是,线程一多,事情就不只是“同时跑”这么简单了,因为它们往往还会共享:

  • 堆上的对象;
  • 某些缓存或集合;
  • 连接池;
  • 任务队列;
  • 外部系统配额。

这意味着,线程从来不是“开出来就行”的廉价能力。线程一旦参与系统,就会把调度、共享状态和上下文切换这些问题一起带进来。

为什么不能随手创建线程?

这是并发入门里一个很重要的边界意识。很多人看到线程,第一反应就是“有任务就 new 一个线程”。问题在于,线程不是免费资源:

  • 创建有成本;
  • 切换有成本;
  • 数量过多会争抢 CPU;
  • 管理混乱时排障极难;
  • 高峰期可能直接把系统压垮。

所以在真实项目里,线程更常被组织进线程池,而不是每来一个任务就临时创建一条新线程。

线程池为什么是基础能力,而不是进阶能力?

因为线程池解决的不是“写法优雅”,而是资源治理问题。它至少帮你解决几件事:

  • 复用线程,减少反复创建销毁成本;
  • 控制并发度,不让任务无限膨胀;
  • 通过队列和拒绝策略管理高峰压力;
  • 让不同类型任务有机会被隔离治理。

也就是说,线程池的意义不是让代码更像框架,而是让系统在任务量上来以后仍然可控。真正重要的不是“会不会调一个线程池”,而是知道:

  • 任务是 CPU 密集还是 I/O 密集;
  • 队列为什么不能无限大;
  • 为什么不同任务最好不要共用同一个池;
  • 为什么线程池治理最终是稳定性问题。

锁到底在保护什么?

锁最容易被误解成“并发时必须上的保护罩”。更准确地说,锁是在共享状态必须被保护时,用来保证一致性的手段。它在回答的是:

  • 某个临界区能不能同时被多线程修改;
  • 某段状态变化是否必须是原子的;
  • 某些操作之间是否必须有顺序和互斥。

真正要警惕的,不是“有没有加锁”,而是:

  • 本来可不可以通过设计避免共享;
  • 锁的粒度是不是过大;
  • 锁里是否做了耗时操作;
  • 调用链里是否潜伏着更大的重复执行问题。

很多同学一学锁,就开始到处加同步,最后系统一致性也没真正变好,吞吐却先掉下去了。因为锁不是银弹,它只是在共享状态无法避免时的一种成本型解法。

并发容器为什么不能被当成“自动线程安全”?

并发容器很有价值,因为它把很多高频共享场景的同步复杂度封装掉了,比如:

  • 并发读写 Map;
  • 多线程消费队列;
  • 某些统计和状态收集场景。

但它真正能保证的,通常只是“这个容器自身的并发访问行为更安全”。它并不自动保证:

  • 你的整个业务流程不会重复执行;
  • 容器里多个操作拼在一起仍然原子;
  • 外围依赖不会出现竞态。

也就是说,并发容器减少的是“手写同步代码”的复杂度,不等于“系统天然安全”。只看容器线程安全,不看业务整体一致性,是非常典型的误区。

放到项目里,并发最常落在哪些场景?

很多同学以为并发只在“底层框架”里,其实业务项目里也处处都能遇到。最常见的场景包括:

  • 异步任务处理;
  • 定时任务执行;
  • 消息消费;
  • 批量任务并行;
  • 请求链路里的共享缓存访问;
  • 大量 I/O 等待下的资源治理。

所以并发并不是“以后再说”的专题,而是 Java 服务端能力里非常现实的一条主线。你越早建立资源竞争意识,后面学线程池治理、异步编排、锁机制和并发工具类时越不会发散。

最容易踩的坑

最常见的问题通常包括:

  • 把线程创建当成廉价操作;
  • 线程池参数照抄别人模板,不结合任务类型;
  • 以为加了锁就万事大吉;
  • 用了并发容器就忽略整体业务一致性;
  • 只想把任务做快,却没有限流、超时和隔离意识。

这些问题的共同点是:它们都只看局部工具,不看整体系统代价。

总结

Java 并发入门真正要建立的,不是对术语的熟练背诵,而是对资源竞争、任务调度和一致性代价的敏感度。线程是执行单元,线程池是治理工具,锁是在共享状态下的成本型手段,并发容器是高频场景的官方封装。只要先把这张资源治理地图建立起来,后面再深入 volatile、AQS、异步编排和线程池治理时,就不会觉得这些内容互相断裂。