一杯咖啡的時(shí)間椒功,帶你徹底理解Java并發(fā)編程的核心概念
引言
作為Java程序員,你一定聽說(shuō)過"可見性"、"原子性"、"有序性"這些概念。但這些抽象的概念往往讓人難以理解粘我。今天,讓我們通過一個(gè)簡(jiǎn)單的辦公室場(chǎng)景痹换,來(lái)重新認(rèn)識(shí)這些概念征字。
一、Java內(nèi)存模型:一個(gè)現(xiàn)代化辦公室
想象一下娇豫,我們的辦公室就是一個(gè)小型的公司:
- 公告板(主內(nèi)存):放在大廳中央
- 員工的記事本(工作內(nèi)存):每個(gè)人自己隨身攜帶
- 各位員工(線程):來(lái)來(lái)往往處理各種事務(wù)
1.1 工作內(nèi)存 vs 線程棧:不同的抽象層面
在討論并發(fā)問題之前柔纵,我們先來(lái)理清兩個(gè)容易混淆的概念:
public class Employee {
private static int companyMoney = 100000; // 公司賬戶余額-主內(nèi)存
public void processPayment() {
int salary = 5000; // 局部變量-線程棧
companyMoney -= salary; // 共享變量-涉及工作內(nèi)存
}
}
線程棧
- 就像員工的個(gè)人辦公桌
- 存放:個(gè)人的文件和工具(局部變量)
- 特點(diǎn):只有自己能用,不需要和別人共享
工作內(nèi)存
- 就像員工的記事本
- 存放:公共信息的備份(共享變量的副本)
- 特點(diǎn):需要和公告板保持同步
1.2 Java內(nèi)存模型的交互規(guī)則
就像公司的規(guī)章制度一樣锤躁,Java內(nèi)存模型定義了幾個(gè)重要的規(guī)則:
- 主內(nèi)存與工作內(nèi)存的交互
class CompanyProcess {
// 主內(nèi)存變量
volatile int documentVersion = 1;
// 交互過程
void updateDocument() {
// 1. 從主內(nèi)存讀取到工作內(nèi)存
// 2. 在工作內(nèi)存中修改
// 3. 寫回主內(nèi)存
documentVersion++;
}
}
- 變量的原子操作
- read:從公告板讀取信息
- load:抄寫到記事本
- use:查看記事本信息
- assign:在記事本修改
- store:準(zhǔn)備更新到公告板
- write:實(shí)際更新公告板
二搁料、并發(fā)編程的三大困擾
2.1 可見性:錯(cuò)過的會(huì)議通知
問題場(chǎng)景
public class MeetingNotice {
private boolean meetingScheduled = false;
// 行政小張:發(fā)布會(huì)議通知
public void scheduleMeeting() {
prepareMeeting();
meetingScheduled = true;
}
// 小李:一直在等通知
public void waitForMeeting() {
while (!meetingScheduled) {
// 不斷查看記事本
}
attendMeeting();
}
}
解決方案
public class MeetingNotice {
// 重要通知必須直接看公告板
private volatile boolean meetingScheduled = false;
// 其他代碼不變
}
2.2 原子性:混亂的會(huì)議室預(yù)訂
問題場(chǎng)景
public class MeetingRoom {
private boolean isBooked = false;
public void book() {
// 以下三步不是原子操作
if (!isBooked) { // 1. 檢查狀態(tài)
validateEmployee(); // 2. 驗(yàn)證員工身份
isBooked = true; // 3. 更新狀態(tài)
}
}
}
解決方案
public class MeetingRoom {
private boolean isBooked = false;
// 加鎖保證原子性
public synchronized void book() {
if (!isBooked) {
validateEmployee();
isBooked = true;
}
}
}
2.3 有序性:混亂的入職流程
問題場(chǎng)景
public class Onboarding {
private boolean workstationReady;
private boolean accountCreated;
public void prepare() {
workstationReady = true; // 可能和下面的順序互換
accountCreated = true;
}
public void checkStatus() {
if (accountCreated && !workstationReady) {
// 賬號(hào)創(chuàng)建了但工位沒準(zhǔn)備好?
}
}
}
解決方案
public class Onboarding {
private volatile boolean workstationReady;
private volatile boolean accountCreated;
public synchronized void prepare() {
workstationReady = true;
accountCreated = true;
}
}
三系羞、深入理解happens-before
就像公司的工作流程一樣郭计,happens-before規(guī)則確定了操作的先后順序:
- 程序順序規(guī)則:
// 代碼中的先后順序
void process() {
step1(); // 一定在step2之前
step2();
}
- volatile變量規(guī)則:
volatile boolean flag;
int data;
// 線程A
data = 42;
flag = true;
// 線程B
if (flag) {
// 一定能看到data = 42
}
- 傳遞性規(guī)則:
// A happens-before B
// B happens-before C
// 則A happens-before C
四、實(shí)踐建議
- 優(yōu)先使用高層同步工具
// 不要這樣
private volatile boolean flag;
private volatile int count;
// 建議這樣
private AtomicBoolean flag = new AtomicBoolean();
private AtomicInteger count = new AtomicInteger();
- 正確使用synchronized
// 同步代碼塊要夠小椒振,但也要確保完整性
synchronized void transfer(Account from, Account to) {
// 放在一個(gè)同步塊中
from.debit(amount);
to.credit(amount);
}
- volatile的適用場(chǎng)景
public class Config {
private volatile String config;
// 適用于單個(gè)變量的讀寫
// 不適用于復(fù)合操作
}
總結(jié)
通過辦公室的日常場(chǎng)景昭伸,我們可以更直觀地理解Java并發(fā)編程中的核心概念:
- 可見性:就像必須及時(shí)查看公告板
- 原子性:就像會(huì)議室預(yù)訂必須一氣呵成
- 有序性:就像入職流程必須按步驟來(lái)
記住,編寫并發(fā)程序時(shí):
- 優(yōu)先使用成熟的并發(fā)工具類
- 理解清楚共享變量的訪問規(guī)則
- 正確使用synchronized和volatile關(guān)鍵字
這些原則不僅適用于Java澎迎,在其他編程語(yǔ)言中也同樣重要庐杨。希望這篇文章能幫助你更好地理解并發(fā)編程的核心概念!
參考資料
- 《Java并發(fā)編程實(shí)戰(zhàn)》
- 《深入理解Java虛擬機(jī)》
- JSR-133: Java Memory Model and Thread Specification