今天学习线程安全这部分是多线程的重点与难点理解好了才能学习下一章节的死锁问题一、 什么是线程安全在单线程环境下运行完全正常的代码在多线程环境下执行却出现了 Bug这就是线程安全问题。二、 五大线程安全问题抢占式执行根本原因 操作系统对线程的调度是随机的不可预测。多个线程同时修改同一个变量一个线程修改一个变量安全。多个线程读取同一个变量安全。多个线程修改不同变量安全。只有“并发修改”会触发危机。操作的非原子性 类似 count 这样的操作在 CPU层面其实分为“读取-修改-保存”三步。如果这三步执行期间被其他线程打断就会出错。注意 简单的赋值操作如 a 10在 Java 中通常是原子的。内存可见性 一个线程对变量的修改另一个线程可能无法立即看到。指令重排序 编译器或 CPU 为了优化性能可能会调整指令的执行顺序导致逻辑在多线程下失效。三、 解决方案使用 synchronized 关键字核心思路是将“非原子”的操作打包成一个“原子”的操作。锁的核心机制加锁Lock线程进入 synchronized 代码块。解锁Unlock 线程执行完代码块或发生异常。阻塞等待 当两个线程竞争同一把锁时后到的线程必须排队等待直到锁被释放。这有效地防止了“插队”现象。没问题我这就帮你把synchronized的具体用法代码化并将死锁部分的逻辑补充完整。这部分非常适合作为博客的“实战与避坑”章节。在 Java 中根据加锁对象的不同主要分为以下三种写法1. 修饰同步代码块常用这是最灵活的写法可以锁定任意指定的对象。publicclassCounter{privatefinalObjectlockernewObject();// 专门的锁对象privateintcount0;publicvoidadd(){synchronized(locker){// 针对 locker 对象加锁count;}}}2. 修饰普通方法实例锁这是一种简化写法此时锁对象等价于this。publicclassCounter{privateintcount0;// 进入方法加锁执行完解锁publicsynchronizedvoidadd(){count;}/* 上述写法等价于 public void add() { synchronized (this) { count; } } */}3. 修饰静态方法类锁当方法被static修饰时锁对象是该类的Class 对象。publicclassCounter{publicsynchronizedstaticvoidfunc(){// 业务逻辑}/* 上述写法等价于 public static void func() { synchronized (Counter.class) { // 业务逻辑 } } */}四、 进阶特性可重入性思考 如果一个线程已经拿到了锁在没释放锁的情况下再次尝试对这把锁加锁会把自己锁死吗这里也引入了死锁的情况下一篇我将重点整理结论 Java 的 synchronized 是可重入的。它内部记录了当前是哪个线程持有了锁并维护了一个计数器。同一个线程再次加锁时计数器加 1解锁时减 1直到为 0 时才真正释放锁。五、总结线程安全是并发编程的基石。通过 synchronized我们牺牲了一定的执行效率引入了阻塞但换取了结果的正确性。下一篇我将整理死锁相关的内容
Java多线程学习(三)
今天学习线程安全这部分是多线程的重点与难点理解好了才能学习下一章节的死锁问题一、 什么是线程安全在单线程环境下运行完全正常的代码在多线程环境下执行却出现了 Bug这就是线程安全问题。二、 五大线程安全问题抢占式执行根本原因 操作系统对线程的调度是随机的不可预测。多个线程同时修改同一个变量一个线程修改一个变量安全。多个线程读取同一个变量安全。多个线程修改不同变量安全。只有“并发修改”会触发危机。操作的非原子性 类似 count 这样的操作在 CPU层面其实分为“读取-修改-保存”三步。如果这三步执行期间被其他线程打断就会出错。注意 简单的赋值操作如 a 10在 Java 中通常是原子的。内存可见性 一个线程对变量的修改另一个线程可能无法立即看到。指令重排序 编译器或 CPU 为了优化性能可能会调整指令的执行顺序导致逻辑在多线程下失效。三、 解决方案使用 synchronized 关键字核心思路是将“非原子”的操作打包成一个“原子”的操作。锁的核心机制加锁Lock线程进入 synchronized 代码块。解锁Unlock 线程执行完代码块或发生异常。阻塞等待 当两个线程竞争同一把锁时后到的线程必须排队等待直到锁被释放。这有效地防止了“插队”现象。没问题我这就帮你把synchronized的具体用法代码化并将死锁部分的逻辑补充完整。这部分非常适合作为博客的“实战与避坑”章节。在 Java 中根据加锁对象的不同主要分为以下三种写法1. 修饰同步代码块常用这是最灵活的写法可以锁定任意指定的对象。publicclassCounter{privatefinalObjectlockernewObject();// 专门的锁对象privateintcount0;publicvoidadd(){synchronized(locker){// 针对 locker 对象加锁count;}}}2. 修饰普通方法实例锁这是一种简化写法此时锁对象等价于this。publicclassCounter{privateintcount0;// 进入方法加锁执行完解锁publicsynchronizedvoidadd(){count;}/* 上述写法等价于 public void add() { synchronized (this) { count; } } */}3. 修饰静态方法类锁当方法被static修饰时锁对象是该类的Class 对象。publicclassCounter{publicsynchronizedstaticvoidfunc(){// 业务逻辑}/* 上述写法等价于 public static void func() { synchronized (Counter.class) { // 业务逻辑 } } */}四、 进阶特性可重入性思考 如果一个线程已经拿到了锁在没释放锁的情况下再次尝试对这把锁加锁会把自己锁死吗这里也引入了死锁的情况下一篇我将重点整理结论 Java 的 synchronized 是可重入的。它内部记录了当前是哪个线程持有了锁并维护了一个计数器。同一个线程再次加锁时计数器加 1解锁时减 1直到为 0 时才真正释放锁。五、总结线程安全是并发编程的基石。通过 synchronized我们牺牲了一定的执行效率引入了阻塞但换取了结果的正确性。下一篇我将整理死锁相关的内容