本文系统梳理 Java 多线程中的核心知识,重点包括:synchronized 锁机制、volatile 内存可见性、线程通信(wait/notify)、线程安全问题。

一 、线程安全问题来源

产生线程安全问题的根本原因:

  • 共享数据
  • 执行顺序不可控,可能发生指令重排
  • 非原子操作

解决方案:

  • synchronized
  • Lock
  • 原子类

二、synchronized 关键字(核心锁机制)

互斥性:同一时刻同一把锁只有一个线程能进入同步代码块

由JVM 层实现:会有一些优化,虽然用了synchronized 关键字,但是会是一个自适应锁的过程。下面是几种优化:

自适应流程无锁 →[某线程进入] →偏向锁→[几个线程竞争] → 轻量级锁→[竞争激烈] → 重量级锁(后面会讲到这几种锁状态,状态只能升级,不能降级)

锁粗化过程:加解锁耗资源,会把多个连续的小锁,合并成一个大锁,

锁消除:如果JVM 发现锁“根本不可能被竞争”,直接把锁去掉(就是无锁)

自动加锁/解锁:进入synchronized 修饰的代码块 → 遇到 ‘{’ 加锁------执行代码块 → 遇到 ’}‘ 解锁

可重入性:同一线程可以多次获取同一把锁,不会死锁,但是只有最外部是真正锁

原理:

·记住持有锁的线程【owner = 当前线程
·每加一次锁计数器+1,解锁计数器-1【count = 重入次数

用法:

①修饰代码块(更灵活 )---只锁关键代码,性能更好

修饰代码块要自己定义一个锁对象,不同线程如果使用的是“同一个锁对象”,才会互斥。即只有一个线程持有锁,这个线程解锁后,别的线程才能用这个锁。

class Counter {
    private int count = 0;
    public void increment() {
        synchronized (this) {  //this 表示锁当前对象,如obj.increment()锁的就是obj
            count++;
        }
    }
}
class Counter {
    private int count = 0;
    private final Object lock = new Object();
    public void increment() {
        synchronized (lock) {  //锁指定对象lock,避免外部代码误用 this 作为锁,常用
            count++;
        }
    }
}

②修饰普通方法(锁当前对象this)---同一时间只有一个线程能执行这个方法,不同对象不会互斥

class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
}

③修饰静态方法(锁类)---锁的是:Counter.class,所有实例共享同一把

class Counter {
    private static int count = 0;

    public static synchronized void increment() {
        count++;
    }
}

三、Java中线程不安全/安全的类

常见线程不安全类 线程安全类
ArrayList Vector(不推荐)
LinkedList Hashtable(不推荐)
HashMap ConcurrentHashMap(推荐)
TreeMap StringBuffer(使用synchronized)
HashSet

有不推荐因为所有方法都加锁

→ 粗粒度锁

→ 性能差

TreeSet
StringBuilder

四、volatile 关键字(保证内存可见性)

解决的问题:可见性 + 有序性(部分)不解决:原子性

· 可见性问题:线程读取变量的流程【主内存 → 工作内存 → CPU使用】

线程A修改变量 → 写到自己缓存 → 没及时刷新到主内存
线程B读取变量 → 读的是旧值 ×

· 有序性问题:JVM 和 CPU 会做优化,也就是指令重排序

a = 1;flag = true;执行顺序可能变为flag = true 先执行,a = 1 后执行(被重排)

· 原子性:volatile int count = 0;
count++; // × 线程不安全

作用:保证从主内存读取变量,保证读到新值√

volatile boolean flag = true;

new Thread(() -> {
    while (flag) {}
    System.out.println("结束");
}).start();

new Thread(() -> {
    flag = false;
});
synchronized vs volatile
对比 synchronized volatile
原子性 ×
可见性
有序性 √(部分)
性能 较低 较高

五、线程通信:wait / notify

为什么需要线程通信:线程是抢占式执行,执行顺序不可控。

核心方法(Object类):

  • wait()   //释放锁,线程进入等待状态,进入等待队列

    【唤醒条件:①notify / notifyAll②超时③interrupt()】

  • notify()   //随机唤醒一个等待线程,不会立即释放锁,当前线程执行完才释放锁
  • notifyAll()   //唤醒所有等待线程,但其他等待的线程仍然需要竞争锁
Object lock = new Object();

new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("等...");
            lock.wait();
            System.out.println("被叫醒了");
        } catch (InterruptedException e) {}
    }
}).start();   //开启线程

new Thread(() -> {
    synchronized (lock) {
        System.out.println("叫一下");
        lock.notify();
    }
}).start();
wait vs sleep
对比 wait sleep
所属类 Object Thread
是否释放锁
使用位置 synchronized内 任意
作用 线程间通信 线程暂停

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐