news 2026/10/22 17:51:57

【Java】线程初阶 中

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java】线程初阶 中

线程安全

线程不安全

  1. //定义静态共享变量count , t1和t2两个线程会同时操作这同一个变量 private static int count=0; public static void main(String[] args) throws InterruptedException { //创建线程1 Thread t1=new Thread(()->{ //循环5000次,每次对count执行自增操作 for(int i=0;i<50000;i++) { count++; } }); //创建线程2 Thread t2=new Thread(()->{ //循环50000次,每次对count执行自增操作 for(int i=0;i<50000;i++) { count++; } }); t1.start(); //启动线程t1,进入就绪状态,等待cpu调度 t2.start(); //启动线程t2,进入就绪状态,等待cpu调度 //join() 方法:让当前线程阻塞,等待调用join的线程执行完毕之后,主线程才继续往下执行 t1.join(); t2.join(); // t2.join();主线程等待t2执行结束 System.out.println("count: "+count); }

  2. 核心:count++ 不是一条原子指令
    count++在 CPU 层面会拆成3 步独立操作:
    (1)load:把 count 的值,从主内存读到 CPU 寄存器
    (2)add:寄存器里的值 + 1
    (3)save:把寄存器新值写回主内存的 count
    原子:不可分割,一步执行完,中间不会被别的线程打断
  3. join() 的作用:
    main 线程阻塞等待 t1、t2 执行完毕,等两个线程全部自增完,再打印 count。
    如果没有 join,main 线程会直接打印,大概率输出 0。
  4. 怎么解决这个线程安全问题
    (1)加 synchronized 同步锁
    synchronized (锁对象) { count++; }
    或给自增方法加synchronized,保证同一时刻只有一个线程执行 count++
    (2)使用原子类AtomicInteger,它里面的自增是 CAS 原子操作,不需要锁
    private static AtomicInteger count = new AtomicInteger(0); count.getAndIncrement(); // 等价 count++
  5. 总结
    线程不安全根本原因:多个线程并发修改同一个共享变量,修改操作不是原子的,加上线程抢占调度,造成指令交错、数据覆盖。

线程安全定义

  • 多线程环境运行结果,和单线程预期结果一样,这个代码就是线程安全的


线程不安全的几个根源

  • 线程是随机抢占式调度(前提条件)
    操作系统什么时候切线程是不确定的,执行顺序有无数种可能。
    代码必须保证任意调度顺序下结果都正确,不然就线程不安全。

  • 多个线程修改同一个共享数据(必要条件)
    只有读共享变量没问题;并发写 / 读写同时发生才会出问题。
  • 原子性
    (1)原子性:一段操作不可分割,执行中途不会被其他线程打断
    (2)例子:把代码块当成房间,原子性 = 一次只能进去一个人,进去之后关上门,别人不能中途闯进来打断。锁(synchronized)就是这个房门锁,实现互斥同步。
  • 可见性
    (1)可见性:一个线程修改共享变量,其他线程可以立刻看到最新值
    (2)JMM 模型规则:
    a)共享变量存在主内存
    b)每个线程有自己独立的工作内存(寄存器 / 缓存)
    c)线程读变量:先拷贝一份副本到自己工作内存,之后读的是副本
    d)线程改变量:先改自己工作内存里的副本,不一定立刻刷回主内存
    (3)现象:线程 1 修改了变量 a,但是没有及时同步回主内存;
    线程 2 一直读自己本地旧副本,永远看不到最新的值,这是可见性问题。
    (4)经典场景:一个线程修改 flag 标记,另一个 while 循环里读 flag,循环停不下来。
    解决方案:volatile关键字,保证可见性,每次读写都和主内存同步。
  • 有序性:
    (1)CPU/JVM 会做指令重排序,为了提高执行效率,调整代码指令执行顺序,
    单线程没问题,多线程下会出现逻辑错乱
    (2)volatile 可以禁止指令重排
    (3)指令重排:JVM、CPU、编译器为了提升执行效率,在不改变单线程执行结果的前提下,打乱代码执行顺序
    (4)多线程下重排会出问题
    a)编译器只保证单线程语义不变,无法预判其他线程的行为。
    b)多线程环境,指令顺序被打乱,就会出现意料之外的错误。
  • 总结三大特性
    (1)原子性:操作不可分割,中途不能被打断 → 靠synchronized/ 锁保证
    (2)可见性:一个线程修改,其他线程及时看到最新值
    →volatile、synchronized 都能保证
    (3)有序性:代码按照我们写的顺序执行,不被 CPU / 编译器乱排
    →volatile禁止重排
    (4)线程不安全 = 多线程并发修改共享变量 + 破坏原子性 / 可见性 / 有序性

  • 区分两个关键字
  • (1)synchronized:保证原子性 + 可见性,会加锁,阻塞其他线程
    (2)volatile:保证可见性、禁止重排序,不能保证原子性


解决线程不安全问题

  • private static int count=0; //共享变量,两个线程都会修改它 public static void main(String[] args)throws InterruptedException { Object locker=new Object(); //锁对象,t1,t2必须共用这同一个锁,才能实现互斥 // 线程t1: 循环5万次,每次对count++自增 Thread t1=new Thread(()->{ for(int i=0;i<50000;i++) { // 同步代码:以locker作为锁 synchronized(locker) { // count++ 不是原子操作,(读取,+1,写回三步) //加锁之后,这三步会整体执行,不会被其他线程打断 count++; } } }); //线程t2:同样循环5万次,自增 Thread t2=new Thread(()->{ for(int i=0;i<50000;i++) { //和t1使用同一个locker锁,同一时刻只有一个线程能进入代码块 synchronized (locker){ count++; } } }); t1.start(); t2.start(); //启动t1线程 // join():主线程等待t1执行完毕,才继续往下走 t1.join(); // join():主线程等待t1执行完毕,才继续往下走 t2.join(); //俩线程执行完后,打印100000 System.out.println("count: "+count); }
  • 核心原理:synchronized 同步锁
    synchronized(锁对象) 是互斥锁:
    (1)同一个锁对象 locker,同一时刻,只能有一个线程拿到锁,
    进入大括号里面的代码执行;
    (2)其他想要拿这个锁的线程,会阻塞等待,
    直到持有锁的线程执行完同步代码块、释放锁;
    (3)把非原子操作 count++ 包裹在同步块里面,
    保证 count++ 这三步(load、add、save)一次性完整执行,
    中途不会被别的线程打断 → 保证原子性;
    (4)同时 synchronized 自带内存屏障:
    释放锁的时候会把工作内存的数据刷回主内存,
    加锁的时候从主内存读取最新值,自动保证可见性,也会禁止指令重排。
  • 重点:两个线程必须用同一个锁对象
    如果 t1 锁 A 对象、t2 锁 B 对象,两把不同的锁,互不影响,
    锁就失效了,依然线程不安全。


synchronized 监视器锁(monitor lock)


synchronized

  • synchronized 是 Java 内置关键字,也叫监视器锁、内置锁,锁信息保存在 Java 对象头中,底层重量级锁依托操作系统 mutex lock 实现,两大核心特性:互斥、可重入


互斥(独占)

  • 同一个对象的锁,同一时刻只能有一个线程持有。
    (1)进入synchronized修饰的代码块 / 方法:加锁
    (2)退出synchronized修饰的代码块 / 方法:解锁
    (3)例子:synchronized void increase(){ count++; }修饰实例方法,
    锁是当前 this 对象

  • 形象理解:
    每个对象自带锁状态(类似厕所:有人 / 无人)
    (1)无人 → 线程占用,标记为有人
    (2)有人 → 其他线程进入阻塞等待队列排队

  • 阻塞等待细节(重点面试点)
    操作系统为这把锁维护等待队列:
    (1)锁被占用时,其他线程获取锁失败,阻塞;
    (2)线程 A 释放锁后,操作系统唤醒等待队列里的线程;
    (3)synchronized 是非公平锁:不遵守先来后到
    比如 A 先拿到锁,B、C 依次排队;
    A 释放锁后,B 和 C 重新竞争,先排队的 B 不一定优先拿到锁


可重入

  • 同一条线程,已经持有这把锁,再次去获取同一把锁,可以成功获取,不会自己把自己卡死(不会死锁)

  • 对比:不可重入锁(会自己死锁)
    lock(); // 第一次加锁成功 lock(); // 第二次再加锁,锁被自己占用,阻塞等待 // 线程卡住,永远无法执行解锁,死锁
  • synchronized 可重入的底层原理
    Monitor 内部维护两个信息:
    (1)持有者 owner:记录当前哪个线程持有锁
    (2)计数器 count:记录重入次数
    1)同一个线程再次获取锁:计数器 + 1
    2)退出同步块:计数器 - 1
    3)只有计数器减到0,锁才算真正释放,其他线程才能抢锁
  • 面试
    (1)锁在哪:对象头里的 monitor 监视器,不是代码;
    不同对象是不同的锁,互不干扰
    (2)互斥:保证原子性,同一对象同时只能一个线程执行同步代码
    (3)可重入:依靠 owner + 计数器实现,支持嵌套同步
    (4)公平性:非公平锁,释放锁后线程竞争,不保证排队顺序
    (5)类型:JVM 层面隐式锁,自动加锁、自动释放,不用手动 unlock
  • 例子:嵌套 synchronized,合法不会死锁
    for (int i = 0; i < 50000; i++) { synchronized (locker) { synchronized (locker) { count++; } } }

synchronized 的 4 种写法


核心:synchronized 锁的是【对象】,不是代码 / 方法;
只有争抢同一个锁对象,才会互斥阻塞
修饰代码块
  • 锁任意对象(自定义锁对象)

    public class SynchronizedDemo { private Object locker = new Object(); public void method() { synchronized (locker) { // 同步代码 } } }

    锁对象:locker 这个 Object 实例

  • 锁当前实例对象 this
    public class SynchronizedDemo { public void method() { synchronized (this) { // 同步代码 } } }

    锁对象:当前实例 this,等价于同步普通成员方法


修饰普通成员方法(实例方法)
  • public class SynchronizedDemo { public synchronized void method() { // 同步代码 } }

    (1)等价写法:synchronized(this)
    (2)锁对象:调用这个方法的实例对象(this)
    (3)不同实例,锁互不干扰;同一个实例,多个synchronized普通方法互斥


修饰 static 静态方法
  • public class SynchronizedDemo { public synchronized static void method() { // 同步代码 } }

    (1)等价写法:synchronized(SynchronizedDemo.class)
    (2)锁对象:类对象(Class 对象),这个类全局唯一
    (3)静态锁 和 实例锁(this)是两把完全不同的锁,不会互斥


面试高频坑点
  • 两个线程竞争同一把锁对象,才会阻塞等待;拿两把不同的锁,互不影响,并行执行

  • 容易错的例子:
    (1)线程 A 调用同一个对象的synchronized普通方法,
    线程 B 调用同一个对象另一个synchronized普通方法 → 抢 this 锁,互斥
    (2)线程 A 调用对象 A 的同步方法,线程 B 调用对象 B 的同步方法
    → 两个 this,两把锁,不互斥
    (3)线程 A 调用静态同步方法(锁 class),线程 B 调用同一个实例的
    普通同步方法(锁 this)→ class 锁 vs this 锁,两把锁,不互斥


Java 标准库 线程安全 、线程不安全类

线程不安全类
  • 这些类在多线程并发修改共享对象的时候,会出现数据错乱、并发异常:
    (1)ArrayList
    (2)LinkedList
    (3)HashMap
    (4)TreeMap
    (5)HashSet
    (6)TreeSet
    (7)StringBuilder

  • 例:StringBuilder 方法没有加锁,多线程同时 append,很容易数据丢失、结果错乱

线程安全类
  • 依靠 synchronized 加锁实现安全(有锁保护,并发性能一般)
    (1)Vector:数组集合,所有方法synchronized,不推荐使用
    (2)HashTable:哈希表,方法synchronized,不推荐使用
    (3)StringBuffer:字符串拼接,核心 append 等方法都带synchronized

    @Override public synchronized StringBuffer append(Object obj) { toStringCache = null; super.append(String.valueOf(obj)); return this; }

    对比:StringBuilder 无锁,不安全;StringBuffer 加 synchronized,安全

  • 不可变类:不加锁,天然线程安全(只读,不能修改)
    (1)String
    a)String 一旦创建,内容不能修改;
    所有看似修改的操作(substring、+ 拼接)都是生成新对象,
    原对象不变,没有写竞争,天然线程安全。
  • 总结区分
    (1)StringBuilder(不安全,无锁) vsStringBuffer(安全,synchronized 锁)
    (2)HashMap(不安全) vsHashTable(安全,过时) vsConcurrentHashMap
    (安全,推荐)
    (3)ArrayList(不安全) vsVector(安全,过时)
    (4)String:不可变,天生线程安全,不靠锁
  • CopyOnWriteArrayList、CopyOnWriteArraySet:写时复制,适合读多写少场景
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 17:51:36

科技兴商下的OPC智能体:广阔市场空间在哪里——专知智库“余行补位”视角下的市场空间重估

科技兴商下的OPC智能体&#xff1a;广阔市场空间在哪里——专知智库“余行补位”视角下的市场空间重估OPC智能体的市场空间&#xff0c;最容易被低估的地方&#xff0c;恰恰是它最容易被误算的地方。如果按传统软件口径测算——“多少家企业、多少采购预算、多少API调用量”——…

作者头像 李华
网站建设 2026/10/22 17:49:16

星途天城后期最强阵容详解 星途天城后期阵容推荐

很多玩家想找一份星途天城后期最强阵容详解&#xff0c;因为打到游戏中后期&#xff0c;总会纠结该把资源倾斜到哪一套队伍。下面为你带来一份星途天城后期最强阵容详解&#xff0c;主流公认成型草系队伍兼顾推图、首领挑战和竞技场&#xff0c;养成曲线平滑&#xff0c;不用硬…

作者头像 李华
网站建设 2026/10/22 17:40:28

Nginx企业实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 17:38:20

(ICLR 2020)MAML 靠的不是快速学习,而是特征复用

文章目录导读背景动机方法框架核心实验&#xff1a;特征复用是 MAML 有效的主导因素衍生算法网络头与主体的分工泛化&#xff1a;元学习的普遍规律总结思考补充导读 论文标题&#xff1a;Rapid Learning or Feature Reuse? Towards Understanding the Effectiveness of MAML&…

作者头像 李华
网站建设 2026/10/22 17:29:13

Ice:macOS 菜单栏图标隐藏管理完全指南,一键快速取回

Ice&#xff1a;macOS 菜单栏图标隐藏管理完全指南&#xff0c;一键快速取回 【免费下载链接】Ice Powerful menu bar manager for macOS 项目地址: https://gitcode.com/GitHub_Trending/ice/Ice 菜单栏挤了二十多个图标&#xff0c;蓝牙和 Wi‑Fi 还被刘海盖住一半——…

作者头像 李华