解決Java中線程安全問題
更新時間:2026年01月12日 08:50:58 作者:我會替風去
線程安全問題源于多個線程并發(fā)訪問共享可變資源,破壞了原子性、可見性或有序性,解決方法包括避免共享可變資源、使用同步/加鎖機制以及volatile關鍵字,本文給大家介紹解決Java中線程安全問題,感興趣的朋友跟隨小編一起看看吧
線程安全問題的核心原因
- 線程安全問題本質是多個線程并發(fā)訪問共享且可變的資源時,操作的原子性、可見性或有序性被破壞,導致程序執(zhí)行結果不符合預期。
- 根本原因:共享可變資源
- 共享資源:多個線程都能訪問到的資源(如成員變量、靜態(tài)變量、共享內存區(qū)域);
- 可變資源:資源的狀態(tài)(值)可以被修改(如int計數(shù)器、HashMap的元素);
- 經(jīng)典的i++ 操作。它在底層分為“讀取-修改-寫入”三個步驟。如果兩個線程同時讀取 i=1,各自加1后寫回,結果是2而不是3。
- 直接原因:三大特性被破壞
- Java內存模型(JMM)定義的多線程并發(fā)三大核心特性,任何一個被破壞都會引發(fā)線程安全問題:
- 原子性:一個操作(如count++)包含“讀 - 改 - 寫”三步,非原子操作會被多線程交錯執(zhí)行;
- 可見性:線程修改共享變量后,不會立即同步到主內存,其他線程讀取的仍是舊值;
- 有序性:JVM的指令重排序優(yōu)化,會導致多線程下執(zhí)行順序混亂(如未加volatile的雙重檢查鎖單例)。
線程安全問題的解決方案
- 核心思路:要么避免共享可變資源(從根源消除問題),要么控制并發(fā)訪問規(guī)則(保證三大特性)。
方案1:避免共享可變資源(優(yōu)先推薦)
- 棧封閉(局部變量):局部變量存儲在線程私有棧中,每個線程有獨立副本,天然線程安全。
public class StackClosedDemo {
// 每個線程調用該方法時,都會創(chuàng)建獨立的count副本
public void calculate() {
int count = 0;
count++; // 無線程安全問題
System.out.println(Thread.currentThread().getName() + ": " + count);
}
public static void main(String[] args) {
StackClosedDemo demo = new StackClosedDemo();
// 10個線程各自操作自己的局部變量
for (int i = 0; i < 10; i++) {
new Thread(demo::calculate, "Thread-" + i).start();
}
}
}- 不可變對象:對象創(chuàng)建后狀態(tài)不可修改(如String、Integer),即使共享也無法修改值。
// 自定義不可變類(final類+final成員變量+無setter)
public final class ImmutableUser {
private final String name;
private final int age;
public ImmutableUser(String name, int age) {
this.name = name;
this.age = age;
}
// 僅提供getter,無setter
public String getName() { return name; }
public int getAge() { return age; }
}- ThreadLocal(線程本地存儲):為每個線程提供獨立的變量副本,線程操作自身副本,互不干擾。
public class ThreadLocalDemo {
// 每個線程有獨立的Integer副本,初始值為0
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public void increment() {
threadLocal.set(threadLocal.get() + 1);
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
// 3個線程各自操作自己的副本
for (int i = 0; i < 3; i++) {
new Thread(() -> {
for (int j = 0; j < 2; j++) {
demo.increment();
}
}, "Thread-" + i).start();
}
}
}
// 輸出(順序可能不同):
// Thread-0: 1、Thread-0: 2
// Thread-1: 1、Thread-1: 2
// Thread-2: 1、Thread-2: 2方案2:同步/加鎖(控制并發(fā)訪問)
互斥同步(阻塞同步):這是最常見的方案,通過加鎖來保證同一時刻只有一個線程操作資源。
- synchronized 關鍵字:Java 原生支持,使用簡單。可修飾方法或代碼塊。屬于不可中斷的鎖。
public class SynchronizedDemo {
private int count = 0;
// 同步實例方法,鎖是this對象
public synchronized void increment() {
count++;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
// 1000個線程執(zhí)行increment
for (int i = 0; i < 1000; i++) {
new Thread(demo::increment).start();
}
Thread.sleep(1000);
System.out.println("最終count:" + demo.count); // 輸出1000
}
}ReentrantLock(顯式鎖):比synchronized靈活(可中斷、可超時、公平鎖),需手動釋放鎖(必須在finally中)。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private int count = 0;
private Lock lock = new ReentrantLock(); // 默認非公平鎖
public void increment() {
lock.lock(); // 加鎖
try {
count++;
} finally {
lock.unlock(); // 釋放鎖,避免死鎖
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo demo = new ReentrantLockDemo();
for (int i = 0; i < 1000; i++) {
new Thread(demo::increment).start();
}
Thread.sleep(1000);
System.out.println("最終count:" + demo.count); // 輸出1000
}
}方案3:volatile關鍵字(保證可見性/有序性)
- 保證可見性:強制失效工作內存,直接讀寫主內存。
- 保證有序性:禁止指令重排序。
- 注意:它不保證原子性(不能解決i++問題)。
public class VolatileDemo {
private volatile boolean stop = false; // 保證可見性和有序性
public void runThread() {
new Thread(() -> {
int i = 0;
while (!stop) { // 能立即感知stop的修改
i++;
}
System.out.println("線程停止,i=" + i);
}).start();
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
demo.runThread();
Thread.sleep(3000);
demo.stop = true; // 修改后,線程立即停止
}
}到此這篇關于Java中線程安全問題的原因和解決方案的文章就介紹到這了,更多相關java線程安全內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot ResponseBody返回值處理的實現(xiàn)
這篇文章主要介紹了SpringBoot ResponseBody返回值處理的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
使用SpringBoot+AOP實現(xiàn)可插拔式日志的示例代碼
這篇文章主要介紹了使用SpringBoot+AOP實現(xiàn)可插拔式日志的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-07-07
SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析
這篇文章主要介紹了SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08
Springboot集成Ehcache3實現(xiàn)本地緩存的配置方法
EhCache是一個純Java的進程內緩存框架,是 Hibernate 中默認的 CacheProvider,同Redis一樣,EhCache 不是純內存緩存,它支持基于內存和磁盤的二級緩存,本文介紹Springboot集成Ehcache3實現(xiàn)本地緩存的配置方法,感興趣的朋友一起看看吧2024-04-04

