【Java】线程安全问题怎么解决(小牛筑讯)

引言:

1.如果你有一个厨房 如何安排厨师有序工作?

1. synchronized 关键字 -> 厨房里的令牌

想象厨房里有一个令牌,只有拿到令牌的厨师才能使用某个灶台(临界区)。其他厨师必须等待这个厨师用完令牌才能拿到令牌使用灶台。
例子:
public class Kitchen {
    private int mealsPrepared = 0;
    
    // 同步方法:就像厨师进入一个独立的厨房小隔间,每次只允许一个厨师进入
    public synchronized void prepareMeal() {
        // 准备餐点
        mealsPrepared++;
    }
    
    // 同步代码块:只有拿到令牌(lock对象)的厨师才能执行代码块
    private final Object lock = new Object();
    public void prepareMealWithBlock() {
        synchronized(lock) {
            mealsPrepared++;
        }
    }
}

2. Lock 接口 -> 更灵活的令牌

Lock就像是一个更智能的令牌,可以设置等待时间,或者可中断的等待。
例子:
​
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
public class Kitchen {
    private int mealsPrepared = 0;
    private final Lock lock = new ReentrantLock();
    
    public void prepareMeal() {
        lock.lock(); // 申请令牌
        try {
            mealsPrepared++;
        } finally {
            lock.unlock(); // 无论怎样都要归还令牌
        }
    }
    
    // 尝试获取令牌,如果等不及1秒就离开
    public boolean tryPrepareMeal() {
        try {
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    mealsPrepared++;
                    return true;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }
}

3. volatile 关键字 -> 公告板

volatile变量就像厨房里的一个公告板,任何厨师更新了公告板(写操作),其他厨师立即能看到最新信息。但是注意,公告板只能保证可见性,不能保证复合操作的原子性。
例子:
public class Kitchen {
    private volatile boolean stopCooking = false;
    
    // 一个厨师设置停止烹饪
    public void setStopCooking() {
        stopCooking = true;
    }
    
    // 其他厨师检查是否停止
    public void checkStop() {
        if (stopCooking) {
            // 停止当前烹饪
        }
    }
}

4. 原子类 -> 自动化的计数器

原子类就像是一个自动化的计数器,多个厨师同时点击计数器,计数器会正确增加,不会出现两个厨师点击一次却只增加一次的情况。
例子:
​
import java.util.concurrent.atomic.AtomicInteger;
​
public class Kitchen {
    private AtomicInteger mealsPrepared = new AtomicInteger(0);
    
    public void prepareMeal() {
        mealsPrepared.incrementAndGet(); // 原子操作,多个厨师同时调用也不会错
    }
}

5. 线程安全集合 -> 专用的储物架

ConcurrentHashMap就像是一个有多层抽屉的储物架,多个厨师可以同时从不同的抽屉拿取或存放食材,而不会互相阻塞。
例子:
import java.util.concurrent.ConcurrentHashMap;
​
public class Kitchen {
    private ConcurrentHashMap<String, Integer> inventory = new ConcurrentHashMap<>();
    
    public void addIngredient(String item, int quantity) {
        inventory.put(item, quantity);
    }
    
    // 多个厨师可以同时调用这些方法而不会互相阻塞
    public int getQuantity(String item) {
        return inventory.getOrDefault(item, 0);
    }
}

6. ThreadLocal -> 每人一套厨具

ThreadLocal就像给每个厨师分配一套独立的厨具,每个厨师只用自己那套,不会和别人争抢。
例子:
​
public class Kitchen {
    private static ThreadLocal<Utensil> utensilHolder = new ThreadLocal<>();
    
    public void setUtensil(Utensil utensil) {
        utensilHolder.set(utensil);
    }
    
    public void useUtensil() {
        Utensil utensil = utensilHolder.get();
        // 使用厨具
    }
}

7. 不可变对象 -> 一次性使用的食材

不可变对象就像是一次性使用的食材包,一旦包装好,里面的食材就不能改变,所以多个厨师可以同时读取这个食材包,而不用担心被别人修改。
例子:
​
public final class MealPackage {
    private final String mainCourse;
    private final String dessert;
    
    public MealPackage(String mainCourse, String dessert) {
        this.mainCourse = mainCourse;
        this.dessert = dessert;
    }
    
    // 只有getter,没有setter
    public String getMainCourse() { return mainCourse; }
    public String getDessert() { return dessert; }
}

8. 并发工具类 -> 厨房里的协调员

CountDownLatch:就像一个任务计数器,完成一个任务就减一,直到为零。
CyclicBarrier:就像一组厨师在某个步骤必须等待所有人都完成才能继续。
例子(CountDownLatch):
​
​
import java.util.concurrent.CountDownLatch;
​
public class Kitchen {
    public void prepareMeal() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 需要完成3个任务
        
        // 三个厨师分别做不同任务
        new Thread(() -> {
            prepareAppetizer();
            latch.countDown();
        }).start();
        
        new Thread(() -> {
            prepareMainCourse();
            latch.countDown();
        }).start();
        
        new Thread(() -> {
            prepareDessert();
            latch.countDown();
        }).start();
        
        latch.await(); // 等待所有任务完成
        serveMeal();
    }
}
例子(CyclicBarrier):
​
​
import java.util.concurrent.CyclicBarrier;
​
public class Kitchen {
    public void prepareMeal() {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            // 当所有厨师都到达屏障时执行
            serveMeal();
        });
        
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                preparePartOfMeal();
                try {
                    barrier.await(); // 等待其他厨师
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

总结

在厨房(多线程环境)中,我们要保证:

  • 互斥:一个灶台一次只能一个厨师用(synchronized, Lock)

  • 可见性:一个厨师更新了公告板,其他厨师立即看到(volatile)

  • 原子性:操作不可中断,比如计数器增加(原子类)

  • 线程封闭:每人用自己的厨具(ThreadLocal)

  • 不可变:食材包一旦包装好就不变(不可变对象)

  • 协调:多个厨师之间的协作(CountDownLatch, CyclicBarrier)

💡 实际选择建议

// 场景1:简单的计数器 → 用原子类
AtomicInteger visitorCount = new AtomicInteger(0);
​
// 场景2:复杂的数据结构操作 → 用锁
ReentrantLock dataLock = new ReentrantLock();
​
// 场景3:状态标志 → 用volatile  
volatile boolean isRunning = true;
​
// 场景4:快速开发 → 用synchronized
public synchronized void quickMethod() { }

记住核心思想:当多个人要修改同一个东西时,要排队或者用原子操作

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值