Java 线程安全详解
线程安全是多线程编程中的核心概念,指在多线程环境下,程序能够正确执行,不会出现数据不一致或异常结果。本文将详细介绍线程安全的实现策略、常见问题及解决方案。
本文内容概览
核心价值
线程安全 = 并发正确性 + 数据一致性 + 可见性 + 原子性 + 有序性
- 🔒 并发控制:确保多线程环境下数据操作的正确性
- 🛡️ 数据保护:防止数据损坏和状态不一致
- 👁️ 可见性:保证一个线程对数据的修改对其他线程可见
- ⚛️ 原子性:确保操作要么完全执行,要么完全不执行
- 🔄 有序性:控制代码执行顺序,防止指令重排导致的问题
1. 线程安全概述
1.1 什么是线程安全?
核心概念
线程安全是指多线程环境下,程序能够正确执行,不会出现数据不一致或异常结果。线程安全的代码在多线程并发执行时,能够保证数据的正确性和一致性。
1.2 线程安全的重要性
线程安全的业务价值
| 重要性 | 具体体现 | 业务价值 |
|---|---|---|
| 数据一致性 | 避免数据损坏和不一致 | 保证业务逻辑正确性 |
| 系统稳定性 | 防止程序崩溃和异常 | 提高系统可用性 |
| 性能优化 | 避免不必要的同步开销 | 提升系统性能 |
| 可维护性 | 代码逻辑清晰,易于调试 | 降低维护成本 |
1.3 线程安全级别
- 不可变对象
- 无状态对象
- 线程安全对象
- 非线程安全对象
java
1/**2 * 1. 不可变对象 - 最高级别的线程安全3 * 对象创建后状态永远不会改变4 */5public static final class ImmutablePoint {6 private final int x;7 private final int y;8 9 public ImmutablePoint(int x, int y) {10 this.x = x;11 this.y = y;12 }13 14 public int getX() { return x; }15 public int getY() { return y; }16 17 // 返回新对象而不是修改现有对象18 public ImmutablePoint move(int dx, int dy) {19 return new ImmutablePoint(x + dx, y + dy);20 }21}2223// 使用示例24ImmutablePoint p1 = new ImmutablePoint(5, 10);25// 移动点位,返回新对象,原对象不变26ImmutablePoint p2 = p1.move(3, 4);27// p1仍然是(5,10),p2是(8,14)特点:
- 所有字段都是final
- 对象状态不可修改
- 所有可变成员变量都进行防御性复制
- 任何方法都不会修改对象状态
- 绝对线程安全,无需同步
java
1/**2 * 2. 无状态对象 - 天然线程安全3 * 不包含任何状态信息4 */5public static class StatelessCalculator {6 public int add(int a, int b) {7 return a + b;8 }9 10 public int multiply(int a, int b) {11 return a * b;12 }13}1415// 使用示例16StatelessCalculator calculator = new StatelessCalculator();17// 多线程调用add和multiply方法是安全的18// 因为没有共享状态可能被修改特点:
- 不包含任何实例或类变量
- 方法的执行结果只依赖于输入参数
- 不依赖外部状态
- 天然线程安全,无需同步
java
1/**2 * 3. 有状态但线程安全的对象3 * 通过同步机制保护状态4 */5public static class ThreadSafeCounter {6 private int count = 0;7 8 // 使用synchronized确保线程安全9 public synchronized void increment() {10 count++;11 }12 13 public synchronized int getCount() {14 return count;15 }16}1718// 或者使用原子类19public static class AtomicCounter {20 private AtomicInteger count = new AtomicInteger(0);21 22 public void increment() {23 count.incrementAndGet();24 }25 26 public int getCount() {27 return count.get();28 }29}特点:
- 包含可变状态
- 通过同步机制(synchronized、Lock、原子类等)保护状态
- 所有访问共享状态的方法都经过同步
- 无需外部同步即可安全使用
java
1/**2 * 4. 有状态且非线程安全的对象3 * 需要外部同步保护4 */5public static class UnsafeCounter {6 private int count = 0;7 8 public void increment() {9 count++; // 非原子操作,线程不安全10 }11 12 public int getCount() {13 return count;14 }15}1617// 使用示例 - 需要外部同步18UnsafeCounter counter = new UnsafeCounter();1920// 不安全的多线程访问21// new Thread(() -> counter.increment()).start();22// new Thread(() -> counter.increment()).start();2324// 安全的使用方式25synchronized(counter) {26 counter.increment();27}特点:
- 包含可变状态
- 没有同步机制保护状态
- 在多线程环境下可能导致数据不一致
- 需要外部同步才能安全使用
2. 线程安全实现策略
线程安全实现策略对比
| 策略 | 描述 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 不可变对象 | 创建后状态不可变 | 最简单安全、无需同步 | 每次操作创建新对象 | 小对象、配置类 |
| 同步机制 | 使用synchronized或Lock | 简单直接、保证原子性 | 性能开销、死锁风险 | 通用场景 |
| 原子变量 | 使用java.util.concurrent.atomic包 | 性能好、无锁 | 仅适用于简单操作 | 计数器、标志位 |
| 线程封闭 | 将数据限制在单线程内使用 | 无需同步 | 限制数据共享 | 请求范围内数据 |
| 线程本地存储 | 使用ThreadLocal | 线程隔离、无竞争 | 可能内存泄漏 | 线程级别缓存 |
| 写时复制 | 修改时创建副本 | 读操作无锁 | 写入开销大 | 读多写少 |
| 栅栏模式 | 使用CountDownLatch等 | 协调多线程执行 | 实现复杂 | 线程协作场景 |
2.1 不可变对象策略
- 不可变字符串
- 不可变配置
- 不可变集合
- 不可变优势
java
1/**2 * 不可变字符串类3 */4public static final class ImmutableString {5 private final char[] value; // final引用,确保不能重新赋值6 7 public ImmutableString(char[] value) {8 // 防御性复制,避免外部修改9 this.value = Arrays.copyOf(value, value.length);10 }11 12 public char[] getValue() {13 // 返回副本,避免外部修改14 return Arrays.copyOf(value, value.length);15 }16 17 public char charAt(int index) {18 if (index < 0 || index >= value.length) {19 throw new IndexOutOfBoundsException();20 }21 return value[index];22 }23 24 public int length() {25 return value.length;26 }27 28 // 字符串连接返回新对象,不修改原对象29 public ImmutableString concat(ImmutableString other) {30 char[] newValue = new char[value.length + other.value.length];31 System.arraycopy(value, 0, newValue, 0, value.length);32 System.arraycopy(other.value, 0, newValue, value.length, other.value.length);33 return new ImmutableString(newValue);34 }35}java
1/**2 * 不可变配置类3 */4public static final class ImmutableConfig {5 private final Map<String, String> properties; // final引用6 7 public ImmutableConfig(Map<String, String> properties) {8 // 创建不可变Map - 两层防御9 // 1. 复制传入的Map,防止外部引用修改10 // 2. 使用Collections.unmodifiableMap确保返回的Map不可修改11 this.properties = Collections.unmodifiableMap(new HashMap<>(properties));12 }13 14 public String getProperty(String key) {15 return properties.get(key);16 }17 18 public Map<String, String> getAllProperties() {19 // 返回不可变视图,不需要再次复制20 // 因为在构造函数中已经使用了unmodifiableMap21 return properties;22 }23 24 // 添加新配置返回新对象,不修改原对象25 public ImmutableConfig withProperty(String key, String value) {26 Map<String, String> newProperties = new HashMap<>(properties);27 newProperties.put(key, value);28 return new ImmutableConfig(newProperties);29 }30}java
1/**2 * 不可变集合类3 */4public static final class ImmutableCollection<T> {5 private final List<T> elements; // final引用6 7 public ImmutableCollection(Collection<T> elements) {8 this.elements = Collections.unmodifiableList(new ArrayList<>(elements));9 }10 11 public List<T> getElements() {12 // 返回不可变视图13 return elements;14 }15 16 public T get(int index) {17 return elements.get(index);18 }19 20 public int size() {21 return elements.size();22 }23 24 // 添加元素返回新集合,不修改原集合25 public ImmutableCollection<T> add(T element) {26 List<T> newElements = new ArrayList<>(elements);27 newElements.add(element);28 return new ImmutableCollection<>(newElements);29 }30 31 // 移除元素返回新集合,不修改原集合32 public ImmutableCollection<T> remove(T element) {33 List<T> newElements = new ArrayList<>(elements);34 newElements.remove(element);35 return new ImmutableCollection<>(newElements);36 }37 38 // 创建一个Builder类来高效构建集合39 public static class Builder<T> {40 private final List<T> elements = new ArrayList<>();41 42 public Builder<T> add(T element) {43 elements.add(element);44 return this;45 }46 47 public Builder<T> addAll(Collection<T> collection) {48 elements.addAll(collection);49 return this;50 }51 52 public ImmutableCollection<T> build() {53 return new ImmutableCollection<>(elements);54 }55 }56}不可变对象的优势:
-
绝对线程安全
- 无需同步
- 无竞态条件
- 无并发修改问题
-
简化开发
- 不需要考虑锁和同步
- 简化并发算法
- 减少错误可能性
-
性能优势
- 无同步开销
- 可以安全缓存
- 适合做键值或Map的键
-
防御性编程
- 避免意外修改
- 状态一致性保证
- 内存泄漏风险降低
2.2 同步机制策略
- 方法同步
- 代码块同步
- ReentrantLock
- 读写锁
java
1/**2 * 方法级同步3 */4public static class MethodSynchronizedCounter {5 private int count = 0;6 7 // 整个方法同步,锁是this对象8 public synchronized void increment() {9 count++;10 }11 12 public synchronized void decrement() {13 count--;14 }15 16 public synchronized int getCount() {17 return count;18 }19 20 // 静态方法同步,锁是类对象(MethodSynchronizedCounter.class)21 public static synchronized void staticMethod() {22 System.out.println("静态同步方法");23 // 操作静态变量24 }25}方法同步特点:
- 锁对象是当前实例(
this)或类对象(Class) - 整个方法体都被同步
- 简单易用,但粒度较粗
- 适合简单场景
java
1/**2 * 代码块同步3 */4public static class BlockSynchronizedCounter {5 private int count = 0;6 private final Object lock = new Object(); // 显式锁对象7 8 public void increment() {9 // 只同步关键代码块10 synchronized (lock) {11 count++;12 }13 // 这里是非同步代码,可以并发执行14 System.out.println("当前计数: " + count);15 }16 17 public void decrement() {18 synchronized (lock) {19 count--;20 }21 }22 23 public int getCount() {24 synchronized (lock) {25 return count;26 }27 }28 29 // 使用专用锁对象30 private final Object readLock = new Object();31 private final Object writeLock = new Object();32 33 public void complexOperation() {34 // 使用不同的锁对象实现更细粒度的控制35 synchronized (readLock) {36 // 读操作37 int currentValue = count;38 39 // 其他线程可以同时获取writeLock40 // 实现更高的并发性41 42 synchronized (writeLock) {43 // 写操作44 count = currentValue + 1;45 }46 }47 }48}代码块同步特点:
- 可以指定任意对象作为锁
- 只锁定必要的代码段
- 可以实现更细粒度的锁控制
- 性能更好,并发性更高
java
1import java.util.concurrent.locks.ReentrantLock;2import java.util.concurrent.TimeUnit;34/**5 * 使用ReentrantLock6 */7public static class ReentrantLockCounter {8 private int count = 0;9 private final ReentrantLock lock = new ReentrantLock();10 11 public void increment() {12 lock.lock(); // 获取锁13 try {14 count++;15 } finally {16 lock.unlock(); // 确保锁被释放17 }18 }19 20 public int getCount() {21 lock.lock();22 try {23 return count;24 } finally {25 lock.unlock();26 }27 }28 29 // Lock接口提供的额外功能30 31 // 1. 可中断获取锁32 public void incrementInterruptibly() throws InterruptedException {33 lock.lockInterruptibly(); // 可以响应中断34 try {35 count++;36 } finally {37 lock.unlock();38 }39 }40 41 // 2. 尝试获取锁42 public boolean tryIncrement() {43 if (lock.tryLock()) { // 尝试获取锁,立即返回结果44 try {45 count++;46 return true;47 } finally {48 lock.unlock();49 }50 }51 return false; // 获取锁失败52 }53 54 // 3. 超时获取锁55 public boolean tryIncrementWithTimeout() {56 try {57 if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试在1秒内获取锁58 try {59 count++;60 return true;61 } finally {62 lock.unlock();63 }64 }65 } catch (InterruptedException e) {66 Thread.currentThread().interrupt();67 }68 return false; // 获取锁超时或被中断69 }70}ReentrantLock特点:
- 比synchronized更灵活
- 支持中断、超时、公平性
- 可以查询锁状态
- 需要显式获取和释放
java
1import java.util.HashMap;2import java.util.Map;3import java.util.concurrent.locks.ReadWriteLock;4import java.util.concurrent.locks.ReentrantReadWriteLock;5import java.util.concurrent.locks.Lock;67/**8 * 读写锁分离9 */10public static class ReadWriteLockCache {11 private final Map<String, String> cache = new HashMap<>();12 private final ReadWriteLock lock = new ReentrantReadWriteLock();13 private final Lock readLock = lock.readLock(); // 读锁 - 共享锁14 private final Lock writeLock = lock.writeLock(); // 写锁 - 排他锁15 16 // 使用读锁 - 允许并发读取17 public String get(String key) {18 readLock.lock();19 try {20 return cache.get(key);21 } finally {22 readLock.unlock();23 }24 }25 26 // 使用写锁 - 独占访问27 public void put(String key, String value) {28 writeLock.lock();29 try {30 cache.put(key, value);31 } finally {32 writeLock.unlock();33 }34 }35 36 // 复合操作 - 读取并有条件地更新37 public String getOrCreate(String key, String defaultValue) {38 // 首先尝试读取39 readLock.lock();40 try {41 String value = cache.get(key);42 if (value != null) {43 return value;44 }45 } finally {46 readLock.unlock(); // 释放读锁47 }48 49 // 如果值不存在,获取写锁创建50 writeLock.lock();51 try {52 // 双重检查,避免在获取写锁期间其他线程已创建53 String value = cache.get(key);54 if (value == null) {55 value = defaultValue;56 cache.put(key, value);57 }58 return value;59 } finally {60 writeLock.unlock();61 }62 }63 64 // 查询缓存大小 - 只读操作65 public int size() {66 readLock.lock();67 try {68 return cache.size();69 } finally {70 readLock.unlock();71 }72 }73}读写锁特点:
- 读锁可以被多个线程同时持有
- 写锁是独占的
- 适合读多写少的场景
- 提高并发性能
2.3 原子操作策略
- 基本原子类
- 引用类型原子类
- 数组原子类
- 字段更新器
java
1import java.util.concurrent.atomic.AtomicInteger;2import java.util.concurrent.atomic.AtomicLong;3import java.util.concurrent.atomic.AtomicBoolean;45/**6 * 基本类型原子类7 */8public static class AtomicCounter {9 private final AtomicInteger count = new AtomicInteger(0);10 private final AtomicLong total = new AtomicLong(0);11 private final AtomicBoolean flag = new AtomicBoolean(false);12 13 // 简单原子操作14 public void increment() {15 count.incrementAndGet(); // 原子自增,返回新值16 }17 18 public void decrement() {19 count.decrementAndGet(); // 原子自减,返回新值20 }21 22 public int getCount() {23 return count.get();24 }25 26 // 复合原子操作27 public void addToTotal(long value) {28 total.addAndGet(value); // 原子加法,返回新值29 }30 31 public boolean setFlagIfNot(boolean expect, boolean update) {32 return flag.compareAndSet(expect, update); // 比较并设置33 }34 35 // 更复杂的原子操作36 public int incrementAndGetWithLimit(int limit) {37 return count.updateAndGet(current -> Math.min(current + 1, limit));38 }39 40 // 线程安全的统计操作41 public void updateStatistics(int value) {42 // 更新最大值43 count.accumulateAndGet(value, Math::max);44 45 // 更新总和46 total.addAndGet(value);47 }48}java
1import java.util.concurrent.atomic.AtomicReference;2import java.util.concurrent.atomic.AtomicStampedReference;34/**5 * 引用类型原子类6 */7public static class AtomicReferenceExample {8 // 使用AtomicReference存储对象引用9 private final AtomicReference<String> message = new AtomicReference<>("初始消息");10 11 // 使用AtomicStampedReference处理ABA问题12 private final AtomicStampedReference<Integer> account = 13 new AtomicStampedReference<>(100, 0); // 初始值100,版本号014 15 public void updateMessage(String newMessage) {16 message.set(newMessage);17 }18 19 public String getMessage() {20 return message.get();21 }22 23 // 原子方式更新对象引用24 public boolean compareAndSetMessage(String expect, String update) {25 return message.compareAndSet(expect, update);26 }27 28 // 处理ABA问题的原子操作29 public boolean withdraw(int amount) {30 int[] stampHolder = new int[1]; // 用于保存当前版本号31 Integer currentBalance = account.get(stampHolder);32 int currentStamp = stampHolder[0];33 34 if (currentBalance >= amount) {35 // 确保余额和版本号都没有变化36 return account.compareAndSet(37 currentBalance, 38 currentBalance - amount,39 currentStamp, 40 currentStamp + 1 // 版本号+141 );42 }43 44 return false;45 }46 47 // 更复杂的原子引用更新48 public void updateMessageIfNecessary(String newMessageIfEmpty) {49 message.updateAndGet(current -> 50 (current == null || current.isEmpty()) ? newMessageIfEmpty : current51 );52 }53}java
1import java.util.concurrent.atomic.AtomicIntegerArray;23/**4 * 数组原子类5 */6public static class AtomicArrayExample {7 // 线程安全的整数数组8 private final AtomicIntegerArray counters;9 10 public AtomicArrayExample(int size) {11 this.counters = new AtomicIntegerArray(size);12 }13 14 // 原子方式递增数组指定位置的值15 public int incrementAndGet(int index) {16 return counters.incrementAndGet(index);17 }18 19 // 获取数组元素20 public int get(int index) {21 return counters.get(index);22 }23 24 // 原子方式更新数组元素25 public boolean compareAndSet(int index, int expect, int update) {26 return counters.compareAndSet(index, expect, update);27 }28 29 // 原子方式添加值30 public int addAndGet(int index, int delta) {31 return counters.addAndGet(index, delta);32 }33 34 // 获取并重置某个位置的计数器35 public int getAndReset(int index) {36 return counters.getAndSet(index, 0);37 }38 39 // 打印所有计数器值40 public void printAll() {41 for (int i = 0; i < counters.length(); i++) {42 System.out.println("Counter[" + i + "] = " + counters.get(i));43 }44 }45}java
1import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;2import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;34/**5 * 字段更新器 - 不创建新对象的情况下实现原子更新字段6 */7public static class FieldUpdaterExample {8 // 需要更新的类9 public static class User {10 // 必须是volatile且访问级别对更新器可见11 public volatile int score;12 public volatile String name;13 14 public User(String name, int score) {15 this.name = name;16 this.score = score;17 }18 19 @Override20 public String toString() {21 return "User{name='" + name + "', score=" + score + '}';22 }23 }24 25 // 创建原子更新器26 private static final AtomicIntegerFieldUpdater<User> SCORE_UPDATER = 27 AtomicIntegerFieldUpdater.newUpdater(User.class, "score");28 29 private static final AtomicReferenceFieldUpdater<User, String> NAME_UPDATER =30 AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");31 32 // 原子方式增加分数33 public static void incrementScore(User user) {34 SCORE_UPDATER.incrementAndGet(user);35 }36 37 // 原子方式更新名称38 public static boolean updateName(User user, String expect, String update) {39 return NAME_UPDATER.compareAndSet(user, expect, update);40 }41 42 public static void main(String[] args) {43 User user = new User("Alice", 100);44 System.out.println("Initial: " + user);45 46 incrementScore(user);47 System.out.println("After increment: " + user);48 49 updateName(user, "Alice", "Alice_Updated");50 System.out.println("After name update: " + user);51 }52}2.4 线程本地存储策略
ThreadLocal基本概念
ThreadLocal是Java中的一种线程隔离机制,它为每个线程提供了一个独立的变量副本,使得每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
主要特点:
- 线程隔离:每个线程都有自己的独立副本
- 避免同步:不需要加锁就能保证线程安全
- 上下文传递:方便地在同一线程内的不同方法之间传递数据
- 减少参数传递:简化方法调用,减少参数传递
使用场景:
- 存储用户身份信息
- 数据库连接管理
- 事务管理
- 请求上下文
- 线程级别缓存
- 基本用法
- 初始值
- 用户上下文
- 继承ThreadLocal
- 内存泄漏问题
java
1/**2 * 基本ThreadLocal使用3 */4public static class ThreadLocalCounter {5 // 定义一个ThreadLocal变量,每个线程都有独立的计数器6 private static final ThreadLocal<Integer> counter = new ThreadLocal<>();7 8 public static void setCounter(int value) {9 counter.set(value);10 }11 12 public static int getCounter() {13 // 处理null值情况14 Integer value = counter.get();15 return value != null ? value : 0;16 }17 18 public static void increment() {19 // 获取当前线程的计数器值20 Integer current = counter.get();21 if (current == null) {22 current = 0;23 }24 // 更新当前线程的计数器值25 counter.set(current + 1);26 }27 28 // 非常重要:清除ThreadLocal值,防止内存泄漏29 public static void remove() {30 counter.remove();31 }32 33 public static void main(String[] args) {34 // 线程135 new Thread(() -> {36 ThreadLocalCounter.setCounter(10);37 ThreadLocalCounter.increment();38 System.out.println("线程1计数器: " + ThreadLocalCounter.getCounter()); // 1139 ThreadLocalCounter.remove(); // 防止内存泄漏40 }).start();41 42 // 线程243 new Thread(() -> {44 ThreadLocalCounter.setCounter(20);45 ThreadLocalCounter.increment();46 System.out.println("线程2计数器: " + ThreadLocalCounter.getCounter()); // 2147 ThreadLocalCounter.remove(); // 防止内存泄漏48 }).start();49 }50}java
1/**2 * 带初始值的ThreadLocal3 */4public static class ThreadLocalWithInitial {5 // 定义带初始值的ThreadLocal,避免空指针异常6 private static final ThreadLocal<Integer> counter = 7 ThreadLocal.withInitial(() -> 0);8 9 private static final ThreadLocal<List<String>> itemList =10 ThreadLocal.withInitial(ArrayList::new);11 12 public static void increment() {13 // 无需处理null值情况,因为有初始值14 counter.set(counter.get() + 1);15 }16 17 public static int getCounter() {18 return counter.get();19 }20 21 public static void addItem(String item) {22 // 直接使用线程本地的List,无需检查null23 itemList.get().add(item);24 }25 26 public static List<String> getItems() {27 return itemList.get();28 }29 30 public static void clear() {31 counter.remove();32 itemList.remove();33 }34}java
1/**2 * 用户上下文ThreadLocal3 */4public static class UserContext {5 // 存储当前线程关联的用户信息6 private static final ThreadLocal<User> userHolder = new ThreadLocal<>();7 8 public static void setUser(User user) {9 userHolder.set(user);10 }11 12 public static User getUser() {13 return userHolder.get();14 }15 16 public static void clear() {17 userHolder.remove();18 }19 20 // 便捷方法,获取用户ID21 public static String getUserId() {22 User user = getUser();23 return user != null ? user.getId() : null;24 }25 26 // 便捷方法,检查用户权限27 public static boolean hasPermission(String permission) {28 User user = getUser();29 return user != null && user.hasPermission(permission);30 }31 32 // 上下文管理,使用try-with-resources模式33 public static class UserContextHolder implements AutoCloseable {34 public UserContextHolder(User user) {35 setUser(user);36 }37 38 @Override39 public void close() {40 clear();41 }42 }43 44 // 使用示例45 public static void processUserRequest(User user) {46 try (UserContextHolder holder = new UserContextHolder(user)) {47 // 在整个方法调用链中都可以访问用户信息48 service1();49 service2();50 service3();51 }52 }53 54 private static void service1() {55 System.out.println("Service1处理用户: " + getUserId());56 }57 58 private static void service2() {59 if (hasPermission("ADMIN")) {60 System.out.println("执行管理员操作");61 }62 }63 64 private static void service3() {65 System.out.println("Service3处理用户: " + getUser().getName());66 }67}java
1/**2 * 可继承的ThreadLocal3 * 子线程可以继承父线程的值4 */5public static class InheritableThreadLocalExample {6 // 普通ThreadLocal - 不会传递给子线程7 private static final ThreadLocal<String> threadLocal = 8 ThreadLocal.withInitial(() -> "初始值");9 10 // InheritableThreadLocal - 会传递给子线程11 private static final InheritableThreadLocal<String> inheritableThreadLocal = 12 new InheritableThreadLocal<String>() {13 @Override14 protected String initialValue() {15 return "可继承的初始值";16 }17 18 @Override19 protected String childValue(String parentValue) {20 // 可以自定义子线程的初始值21 return parentValue + " (子线程继承)";22 }23 };24 25 public static void main(String[] args) {26 // 设置父线程的值27 threadLocal.set("父线程值");28 inheritableThreadLocal.set("父线程可继承值");29 30 // 创建子线程31 new Thread(() -> {32 // 普通ThreadLocal无法继承父线程的值33 System.out.println("子线程 threadLocal: " + threadLocal.get());34 // InheritableThreadLocal可以继承父线程的值35 System.out.println("子线程 inheritableThreadLocal: " + inheritableThreadLocal.get());36 }).start();37 38 // 父线程输出39 System.out.println("父线程 threadLocal: " + threadLocal.get());40 System.out.println("父线程 inheritableThreadLocal: " + inheritableThreadLocal.get());41 }42}ThreadLocal潜在的内存泄漏问题:
-
问题原因
- ThreadLocal变量存储在Thread对象的ThreadLocalMap中
- ThreadLocalMap使用ThreadLocal作为key的弱引用
- 如果ThreadLocal没有强引用,但Thread仍存活,则可能导致内存泄漏
-
防止内存泄漏的最佳实践
- 在不需要ThreadLocal变量时,务必调用remove()方法
- 使用try-finally确保remove()被调用
- 对于线程池场景,特别要注意清理ThreadLocal变量
java
1// 正确使用ThreadLocal的模式2ThreadLocal<Resource> resourceHolder = new ThreadLocal<>();3try {4 resourceHolder.set(createResource());5 // 使用资源6 useResource();7} finally {8 // 关键: 不再需要时移除ThreadLocal变量9 resourceHolder.remove();10}3. 常见线程安全问题
3.1 竞态条件(Race Condition)
竞态条件是指多个线程同时访问和修改共享数据时出现的问题。
竞态条件示例
java
1public class RaceConditionExamples {2 3 /**4 * 不安全的计数器 - 存在竞态条件5 */6 public static class UnsafeCounter {7 private int count = 0;8 9 public void increment() {10 // 非原子操作:读取 -> 修改 -> 写入11 count++;12 }13 14 public void decrement() {15 count--;16 }17 18 public int getCount() {19 return count;20 }21}2223 /**24 * 安全的计数器 - 使用同步解决竞态条件25 */26 public static class SafeCounter {27 private int count = 0;28 private final Object lock = new Object();29 30 public void increment() {31 synchronized (lock) {32 count++;33 }34 }35 36 public void decrement() {37 synchronized (lock) {38 count--;39 }40 }41 42 public int getCount() {43 synchronized (lock) {44 return count;45 }46 }47 }48 49 /**50 * 使用原子类解决竞态条件51 */52 public static class AtomicCounter {53 private AtomicInteger count = new AtomicInteger(0);54 55 public void increment() {56 count.incrementAndGet();57 }58 59 public void decrement() {60 count.decrementAndGet();61 }62 63 public int getCount() {64 return count.get();65 }66 }67 68 /**69 * 复合操作的竞态条件70 */71 public static class UnsafeBankAccount {72 private double balance = 1000.0;73 74 public void withdraw(double amount) {75 if (balance >= amount) {76 // 检查和取款之间存在竞态条件77 try {78 Thread.sleep(100); // 模拟处理时间79 } catch (InterruptedException e) {80 Thread.currentThread().interrupt();81 }82 balance -= amount;83 System.out.println("取款成功: " + amount + ", 余额: " + balance);84 } else {85 System.out.println("余额不足");86 }87 }88 89 public double getBalance() {90 return balance;91 }92 }93 94 /**95 * 解决复合操作竞态条件96 */97 public static class SafeBankAccount {98 private double balance = 1000.0;99 private final Object lock = new Object();100 101 public void withdraw(double amount) {102 synchronized (lock) {103 if (balance >= amount) {104 try {105 Thread.sleep(100); // 模拟处理时间106 } catch (InterruptedException e) {107 Thread.currentThread().interrupt();108 }109 balance -= amount;110 System.out.println("取款成功: " + amount + ", 余额: " + balance);111 } else {112 System.out.println("余额不足");113 }114 }115 }116 117 public double getBalance() {118 synchronized (lock) {119 return balance;120 }121 }122 }123}3.2 内存可见性问题
一个线程对共享变量的修改对其他线程不可见。
内存可见性问题示例
java
1public class VisibilityProblemExamples {2 3 /**4 * 可见性问题示例5 */6 public static class VisibilityProblem {7 private boolean flag = false;8 9 public void setFlag() {10 flag = true;11 }12 13 public boolean getFlag() {14 return flag;15 }16}1718 /**19 * 使用volatile解决可见性问题20 */21 public static class VisibilitySolution {22 private volatile boolean flag = false;23 24 public void setFlag() {25 flag = true;26 }27 28 public boolean getFlag() {29 return flag;30 }31 }32 33 /**34 * 使用synchronized解决可见性问题35 */36 public static class SynchronizedVisibility {37 private boolean flag = false;38 private final Object lock = new Object();39 40 public void setFlag() {41 synchronized (lock) {42 flag = true;43 }44 }45 46 public boolean getFlag() {47 synchronized (lock) {48 return flag;49 }50 }51 }52 53 /**54 * 使用原子类解决可见性问题55 */56 public static class AtomicVisibility {57 private AtomicBoolean flag = new AtomicBoolean(false);58 59 public void setFlag() {60 flag.set(true);61 }62 63 public boolean getFlag() {64 return flag.get();65 }66 }67 68 /**69 * 双重检查锁定模式70 */71 public static class DoubleCheckedLocking {72 private volatile static DoubleCheckedLocking instance;73 74 private DoubleCheckedLocking() {}75 76 public static DoubleCheckedLocking getInstance() {77 if (instance == null) {78 synchronized (DoubleCheckedLocking.class) {79 if (instance == null) {80 instance = new DoubleCheckedLocking();81 }82 }83 }84 return instance;85 }86 }87}3.3 死锁问题
多个线程互相等待对方释放资源。
死锁问题示例
java
1public class DeadlockExamples {2 3 /**4 * 经典死锁示例5 */6 public static class DeadlockExample {7 private final Object lock1 = new Object();8 private final Object lock2 = new Object();9 10 public void method1() {11 synchronized (lock1) {12 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁1");13 try {14 Thread.sleep(100); // 模拟处理时间15 } catch (InterruptedException e) {16 Thread.currentThread().interrupt();17 }18 19 synchronized (lock2) {20 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁2");21 }22 }23 }24 25 public void method2() {26 synchronized (lock2) {27 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁2");28 try {29 Thread.sleep(100); // 模拟处理时间30 } catch (InterruptedException e) {31 Thread.currentThread().interrupt();32 }33 34 synchronized (lock1) {35 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁1");36 }37 }38 }39 }40 41 /**42 * 解决死锁 - 固定锁顺序43 */44 public static class SafeLockExample {45 private final Object lock1 = new Object();46 private final Object lock2 = new Object();47 48 public void method1() {49 synchronized (lock1) {50 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁1");51 try {52 Thread.sleep(100);53 } catch (InterruptedException e) {54 Thread.currentThread().interrupt();55 }56 57 synchronized (lock2) {58 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁2");59 }60 }61 }62 63 public void method2() {64 // 使用相同的锁顺序65 synchronized (lock1) {66 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁1");67 try {68 Thread.sleep(100);69 } catch (InterruptedException e) {70 Thread.currentThread().interrupt();71 }72 73 synchronized (lock2) {74 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁2");75 }76 }77 }78 }79 80 /**81 * 解决死锁 - 使用超时机制82 */83 public static class TimeoutLockExample {84 private final ReentrantLock lock1 = new ReentrantLock();85 private final ReentrantLock lock2 = new ReentrantLock();86 87 public void method1() {88 if (lock1.tryLock()) {89 try {90 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁1");91 Thread.sleep(100);92 93 if (lock2.tryLock(5, TimeUnit.SECONDS)) {94 try {95 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁2");96 } finally {97 lock2.unlock();98 }99 } else {100 System.out.println("线程 " + Thread.currentThread().getName() + " 获取锁2超时");101 }102 } catch (InterruptedException e) {103 Thread.currentThread().interrupt();104 } finally {105 lock1.unlock();106 }107 }108 }109 110 public void method2() {111 if (lock2.tryLock()) {112 try {113 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁2");114 Thread.sleep(100);115 116 if (lock1.tryLock(5, TimeUnit.SECONDS)) {117 try {118 System.out.println("线程 " + Thread.currentThread().getName() + " 获得锁1");119 } finally {120 lock1.unlock();121 }122 } else {123 System.out.println("线程 " + Thread.currentThread().getName() + " 获取锁1超时");124 }125 } catch (InterruptedException e) {126 Thread.currentThread().interrupt();127 } finally {128 lock2.unlock();129 }130 }131 }132 }133}4. 线程安全最佳实践
4.1 设计原则
核心原则
设计线程安全代码时,应遵循以下原则:
- 优先使用不可变对象:避免状态变化带来的复杂性
- 最小化同步范围:只同步必要的代码块
- 使用线程安全的集合:避免手动同步
- 避免嵌套锁:防止死锁
- 正确使用volatile:理解其局限性
4.2 代码实践
线程安全最佳实践示例
java
1public class ThreadSafetyBestPractices {2 3 /**4 * 1. 优先使用不可变对象5 */6 public static final class ImmutableConfiguration {7 private final Map<String, String> config;8 9 public ImmutableConfiguration(Map<String, String> config) {10 this.config = Collections.unmodifiableMap(new HashMap<>(config));11 }12 13 public String getValue(String key) {14 return config.get(key);15 }16 17 public ImmutableConfiguration withValue(String key, String value) {18 Map<String, String> newConfig = new HashMap<>(config);19 newConfig.put(key, value);20 return new ImmutableConfiguration(newConfig);21 }22 }23 24 /**25 * 2. 使用线程安全的集合26 */27 public static class ThreadSafeCollections {28// 推荐:使用线程安全的集合29 private final Map<String, String> safeMap = new ConcurrentHashMap<>();30 private final List<String> safeList = Collections.synchronizedList(new ArrayList<>());31 private final Queue<String> safeQueue = new ConcurrentLinkedQueue<>();32 33 public void addToMap(String key, String value) {34 safeMap.put(key, value);35 }36 37 public void addToList(String item) {38 safeList.add(item);39 }40 41 public void addToQueue(String item) {42 safeQueue.offer(item);43 }44 }45 46 /**47 * 3. 最小化同步范围48 */49 public static class MinimizedSynchronization {50 private final Object lock = new Object();51 private int counter = 0;52 53 // 不推荐:同步整个方法54public synchronized void badMethod() {55 // 耗时操作56 try {57 Thread.sleep(1000);58 } catch (InterruptedException e) {59 Thread.currentThread().interrupt();60 }61 // 只有这一行需要同步62 counter++;63}6465// 推荐:只同步必要的部分66public void goodMethod() {67 // 耗时操作不需要同步68 try {69 Thread.sleep(1000);70 } catch (InterruptedException e) {71 Thread.currentThread().interrupt();72 }73 74 // 只同步关键部分75 synchronized (lock) {76 counter++;77 }78 }79 }80 81 /**82 * 4. 使用原子类83 */84 public static class AtomicBestPractices {85 private final AtomicInteger counter = new AtomicInteger(0);86 private final AtomicReference<String> reference = new AtomicReference<>("initial");87 88 public void increment() {89 counter.incrementAndGet();90 }91 92 public void updateReference(String newValue) {93 reference.set(newValue);94 }95 96 public boolean compareAndSetReference(String expect, String update) {97 return reference.compareAndSet(expect, update);98 }99 }100 101 /**102 * 5. 正确的异常处理103 */104 public static class ExceptionHandling {105 private final ReentrantLock lock = new ReentrantLock();106 private int value = 0;107 108 public void safeIncrement() {109 lock.lock();110 try {111 value++;112 // 可能抛出异常的操作113 if (value > 100) {114 throw new RuntimeException("值过大");115 }116 } finally {117 lock.unlock(); // 确保锁被释放118 }119 }120 }121}5. 性能优化技巧
5.1 减少锁竞争
减少锁竞争示例
java
1public class LockContentionOptimization {2 3 /**4 * 锁分段技术5 */6 public static class StripedCounter {7 private final int segments = 16;8 private final Object[] locks = new Object[segments];9 private final int[] counters = new int[segments];10 11 public StripedCounter() {12 for (int i = 0; i < segments; i++) {13 locks[i] = new Object();14 }15 }16 17 public void increment(int key) {18 int segment = Math.abs(key % segments);19 synchronized (locks[segment]) {20 counters[segment]++;21 }22 }23 24 public int getTotal() {25 int total = 0;26 for (int i = 0; i < segments; i++) {27 synchronized (locks[i]) {28 total += counters[i];29 }30 }31 return total;32 }33 }34 35 /**36 * 读写锁分离37 */38 public static class ReadWriteOptimizedCache {39 private final Map<String, String> cache = new HashMap<>();40 private final ReadWriteLock lock = new ReentrantReadWriteLock();41 private final Lock readLock = lock.readLock();42 private final Lock writeLock = lock.writeLock();43 44 public String get(String key) {45 readLock.lock();46 try {47 return cache.get(key);48 } finally {49 readLock.unlock();50 }51 }52 53 public void put(String key, String value) {54 writeLock.lock();55 try {56 cache.put(key, value);57 } finally {58 writeLock.unlock();59 }60 }61 }62}5.2 无锁编程
无锁编程示例
java
1public class LockFreeProgramming {2 3 /**4 * 无锁栈实现5 */6 public static class LockFreeStack<T> {7 private AtomicReference<Node<T>> top = new AtomicReference<>();8 9 public void push(T item) {10 Node<T> newHead = new Node<>(item);11 Node<T> oldHead;12 do {13 oldHead = top.get();14 newHead.next = oldHead;15 } while (!top.compareAndSet(oldHead, newHead));16 }17 18 public T pop() {19 Node<T> oldHead;20 Node<T> newHead;21 do {22 oldHead = top.get();23 if (oldHead == null) {24 return null;25 }26 newHead = oldHead.next;27 } while (!top.compareAndSet(oldHead, newHead));28 return oldHead.item;29 }30 31 private static class Node<T> {32 final T item;33 Node<T> next;34 35 Node(T item) {36 this.item = item;37 }38 }39 }40 41 /**42 * 无锁队列实现43 */44 public static class LockFreeQueue<T> {45 private AtomicReference<Node<T>> head = new AtomicReference<>();46 private AtomicReference<Node<T>> tail = new AtomicReference<>();47 48 public LockFreeQueue() {49 Node<T> dummy = new Node<>(null);50 head.set(dummy);51 tail.set(dummy);52 }53 54 public void enqueue(T item) {55 Node<T> newNode = new Node<>(item);56 while (true) {57 Node<T> last = tail.get();58 Node<T> next = last.next.get();59 if (last == tail.get()) {60 if (next == null) {61 if (last.next.compareAndSet(null, newNode)) {62 tail.compareAndSet(last, newNode);63 return;64 }65 } else {66 tail.compareAndSet(last, next);67 }68 }69 }70 }71 72 public T dequeue() {73 while (true) {74 Node<T> first = head.get();75 Node<T> last = tail.get();76 Node<T> next = first.next.get();77 if (first == head.get()) {78 if (first == last) {79 if (next == null) {80 return null;81 }82 tail.compareAndSet(last, next);83 } else {84 T item = next.item;85 if (head.compareAndSet(first, next)) {86 return item;87 }88 }89 }90 }91 }92 93 private static class Node<T> {94 final T item;95 final AtomicReference<Node<T>> next = new AtomicReference<>();96 97 Node(T item) {98 this.item = item;99 }100 }101 }102}6. 总结
线程安全是Java并发编程的核心概念,掌握线程安全的实现策略和最佳实践对于构建高质量的并发应用至关重要。
6.1 关键要点
- 线程安全策略:不可变对象、同步机制、原子操作、线程本地存储
- 常见问题:竞态条件、内存可见性、死锁
- 最佳实践:优先使用不可变对象、最小化同步范围、使用线程安全集合
- 性能优化:减少锁竞争、无锁编程、读写锁分离
6.2 学习建议
- 理解原理:深入理解各种线程安全机制的工作原理
- 实践验证:通过编写代码验证不同策略的效果
- 性能测试:对比不同实现方式的性能差异
- 持续学习:关注新的线程安全技术和最佳实践
通过深入理解和熟练运用这些线程安全技术,我们能够构建出更加高效、健壮和可维护的Java并发应用程序。
评论