首先,先明确一个前提:线程安全修改共享变量的核心,是解决三个问题——原子性(操作不可分割)、可见性(一个线程修改后,其他线程能立刻看到)、有序性(避免指令重排导致的逻辑混乱)。死锁源于锁的不合理嵌套和循环等待,性能瓶颈则多来自锁的粒度太大、线程阻塞开销过高。

下面从「方案选择」「避免死锁」「性能优化」三个维度展开,给出可落地的解决方案。


一、核心解决方案:从「悲观锁」到「乐观锁」

根据并发压力和业务场景,优先选择「轻量方案」(无锁),再考虑「重量方案」(有锁),这样能最大程度平衡线程安全和性能。

方案1:悲观锁(独占锁)—— 简单易用,适合低到中并发

悲观锁的核心思想:假设一定会有线程竞争,先获取锁,再执行操作,同一时间只允许一个线程修改共享变量,天然保证原子性、可见性、有序性。

1.1 synchronized 关键字(JVM内置锁)

这是Java中最基础的同步机制,使用简单,无需手动管理锁的释放。

  • 原理:属于「可重入悲观锁」,JVM层面实现,会自动完成「加锁-执行-释放锁」(即使发生异常,也会自动释放锁,降低死锁风险)。
  • 优化:现代JVM对synchronized做了大量优化(偏向锁→轻量级锁→重量级锁的升级流程),低并发下性能接近无锁方案。
  • 用法:推荐使用「同步代码块」(缩小锁粒度),而非「同步方法」(锁粒度太大,性能差)。
  • 线程安全保障:同一时间只有一个线程能进入同步代码块,修改共享变量。
  • 死锁风险:低,但如果嵌套synchronized锁获取顺序不一致,仍会产生死锁。

示例代码(安全修改计数器)

public class SynchronizedDemo {
    // 共享变量
    private int count = 0;

    // 安全修改共享变量的方法
    public void increment() {
        // 同步代码块:锁对象推荐使用「当前类的实例」或「类对象」(按需选择)
        synchronized (this) {
            count++; // 原子操作(在同步块内,避免被其他线程打断)
        }
    }

    // 获取结果
    public int getCount() {
        // 读取共享变量也建议保证可见性(synchronized 或 volatile)
        synchronized (this) {
            return count;
        }
    }
}
1.2 ReentrantLock(显式锁,java.util.concurrent.locks)

这是比synchronized更灵活的悲观锁,API层面实现,适合复杂业务场景(如超时获取锁、可中断锁)。

  • 原理:属于「可重入悲观锁」,支持公平锁/非公平锁(默认非公平,性能更高)。
  • 核心优势:
    1. 手动控制锁的获取和释放(必须在finally块中释放锁,避免锁泄露);
    2. 提供tryLock(long timeout, TimeUnit unit)方法,支持超时获取锁,可有效避免死锁;
    3. 支持lockInterruptibly(),可中断等待锁的线程。
  • 性能:高并发下与synchronized差距不大(JVM优化后),但灵活性远超synchronized

示例代码(安全修改计数器,避免死锁)

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    private int count = 0;
    // 创建可重入锁(非公平锁)
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        // 1. 获取锁(可选择tryLock带超时,避免死锁)
        boolean isLocked = false;
        try {
            // 尝试获取锁,超时1秒则放弃,返回false
            isLocked = lock.tryLock(1, java.util.concurrent.TimeUnit.SECONDS);
            if (isLocked) {
                count++;
            } else {
                // 获取锁失败,可做降级处理(如记录日志、重试)
                System.out.println("获取锁超时,放弃本次修改");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 2. 必须在finally中释放锁,避免锁泄露
            if (isLocked) {
                lock.unlock();
            }
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

方案2:乐观锁(无锁机制)—— 适合高并发,无死锁

乐观锁的核心思想:假设没有线程竞争,不获取锁,直接尝试修改共享变量,修改前验证变量是否被其他线程修改过,若未被修改则更新,否则重试

这种方案无线程阻塞,没有死锁风险,高并发下性能远优于悲观锁。

2.1 CAS 机制(Compare-And-Swap,比较并交换)

CAS是乐观锁的底层实现,由CPU硬件提供原子指令支持(无需JVM介入),保证操作的原子性。

  • CAS的三个核心操作数:
    1. 内存值V(共享变量的当前内存地址值);
    2. 预期值A(线程读取到的共享变量初始值);
    3. 更新值B(线程要修改后的目标值)。
  • 执行逻辑:当且仅当V == A时,CPU才会将V更新为B,否则不做任何操作;线程会循环重试(自旋),直到更新成功(或达到自旋上限)。
  • 注意事项:
    1. 需用volatile修饰共享变量,保证可见性和有序性(禁止指令重排,让线程能读取到最新的内存值);
    2. 存在「ABA问题」(变量被修改为B后又改回A,CAS会误认为未被修改),可通过「版本号」解决(如AtomicStampedReference);
    3. 自旋次数过多会导致CPU占用过高,高并发竞争激烈时需优化。
2.2 Java Atomic 系列类(直接使用CAS,无需手动实现)

Java提供了java.util.concurrent.atomic包,封装了CAS操作,直接使用即可实现无锁线程安全,无需手动处理自旋和volatile。

  • 常用类:AtomicIntegerAtomicLongAtomicBooleanAtomicReference(支持复杂对象)。
  • 线程安全保障:底层CAS+volatile,保证原子性、可见性、有序性。
  • 死锁风险:无(无锁机制,不存在锁的竞争和等待)。
  • 性能:高并发下性能优异,无线程阻塞/唤醒开销。

示例代码(无锁安全修改计数器)

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    // 共享变量:使用AtomicInteger封装,无需额外加锁
    private final AtomicInteger count = new AtomicInteger(0);

    // 安全修改共享变量:无锁,底层CAS
    public void increment() {
        // getAndIncrement():原子性i++,返回修改前的值
        count.getAndIncrement();
    }

    // 获取结果:原子性读取
    public int getCount() {
        return count.get();
    }
}
2.3 LongAdder(超高并发优化,解决Atomic系列自旋竞争)

当并发量极高时,AtomicLong的自旋CAS会因为竞争激烈导致大量重试,CPU占用过高。LongAdder通过「分段累加」优化了这个问题。

  • 原理:将一个共享变量拆分为多个「子变量」(cells数组),每个线程优先修改自己对应的子变量,最后汇总所有子变量的值得到最终结果,大大降低CAS竞争。
  • 适用场景:高并发下的「计数场景」(如统计接口访问量、订单数),性能远超AtomicLong
  • 死锁风险:无。

示例代码(超高并发计数器)

import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {
    private final LongAdder count = new LongAdder();

    public void increment() {
        // 累加,底层分段CAS,无锁
        count.increment();
    }

    public long getCount() {
        // 汇总所有子变量,返回最终结果
        return count.sum();
    }
}

二、如何避免死锁?

死锁的四个必要条件:互斥条件请求与保持条件不可剥夺条件循环等待条件。只要破坏其中一个,就能避免死锁,实践中常用以下可落地的原则:

  1. 统一锁的获取顺序(破坏「循环等待条件」)
    多个线程需要获取多个锁时,必须按照「相同的固定顺序」获取(比如按锁对象的哈希值从小到大、按业务ID从小到大),避免循环等待。
    示例:线程1和线程2都需要获取锁A和锁B,统一先获取锁A,再获取锁B。

  2. 避免锁的嵌套使用(减少「请求与保持条件」的触发)
    尽量减少「锁中锁」的嵌套逻辑,嵌套层数越多,死锁风险越高,且排查难度越大。如果必须嵌套,严格遵守「统一锁顺序」。

  3. 使用可超时的锁(破坏「不可剥夺条件」)
    优先使用ReentrantLocktryLock(long timeout, TimeUnit unit),设置合理的超时时间,超时后自动放弃获取锁,避免线程无限等待。

  4. 减少锁的持有时间
    获取锁后,只执行「核心的共享变量修改逻辑」,耗时操作(如IO、网络请求、复杂计算)放在锁外执行,降低锁的竞争时间,间接减少死锁概率。

  5. 避免使用不可中断的锁
    使用ReentrantLocklockInterruptibly()方法,支持线程中断,当检测到死锁风险时,可以中断等待锁的线程,释放资源。


三、如何避免性能瓶颈?

性能瓶颈的核心来源:锁粒度太大线程阻塞开销过高CAS竞争激烈,对应的优化策略如下:

  1. 优先选择「无锁方案」(CAS/Atomic/LongAdder)
    高并发场景下,无锁方案没有线程阻塞/唤醒的开销,性能远优于悲观锁,是避免性能瓶颈的首选。

  2. 缩小锁粒度(将「大锁」拆分为「小锁」)
    不直接锁整个对象或整个方法,只锁「需要修改的共享变量相关逻辑」(如synchronized同步代码块代替同步方法)。
    典型案例:ConcurrentHashMap的「分段锁」(JDK1.7)/「CAS+synchronized分段」(JDK1.8),将整个Map拆分为多个段,每个段单独加锁,多线程可同时操作不同段,提升并发性能。

  3. 减少锁的持有时间
    锁内只保留「原子性修改共享变量」的核心代码,耗时操作(如数据库操作、JSON序列化、网络请求)全部移出锁外,降低线程等待锁的时间。

  4. 避免不必要的共享(使用ThreadLocal)
    如果共享变量可以按线程隔离,优先使用ThreadLocal为每个线程创建独立副本,线程修改自己的副本,无需竞争,既保证线程安全,又无性能开销。
    示例:Spring的事务管理器、日期格式化工具SimpleDateFormat的线程安全问题,都可以用ThreadLocal解决。

  5. 优化CAS自旋(避免CPU占用过高)
    无限自旋会导致CPU飙升,可通过以下方式优化:

    • 设置自旋次数上限(超过次数则放弃,或切换为悲观锁);
    • 高并发下使用LongAdder代替AtomicLong,通过分段累加降低CAS竞争;
    • 结合「自适应自旋」(JVM内置优化,根据前一次自旋是否成功调整自旋次数)。
  6. 使用并发容器代替自定义同步容器
    优先使用JUC提供的并发容器(ConcurrentHashMapCopyOnWriteArrayListLinkedBlockingQueue),这些容器已经做了高度优化,比自己用「普通容器+synchronized」性能更好。


总结

  1. 线程安全修改共享变量的核心:解决原子性、可见性、有序性,优先选择「无锁方案」(CAS/Atomic/LongAdder),低并发场景可选择「悲观锁」(synchronized/ReentrantLock)。
  2. 避免死锁的关键:统一锁顺序、避免锁嵌套、使用可超时锁,破坏死锁的四个必要条件之一即可。
  3. 避免性能瓶颈的核心:缩小锁粒度、减少锁持有时间、优先无锁、避免不必要共享,平衡线程安全和并发性能。
方案适用场景死锁风险性能表现
synchronized低到中并发、简单场景较好(JVM优化后)
ReentrantLock中并发、复杂业务(超时/中断)中(可规避)较好
Atomic 系列高并发、简单变量(int/long)优秀
LongAdder超高并发、计数场景最优
ThreadLocal线程隔离、无需共享副本极致(无竞争)

更多推荐