f 数据仓库与分析-Java 多线程进阶:线程安全、synchronized、死锁、wait/notify 全解析(含代码示例) - Powered by qidao123.com技术社区

Java 多线程进阶:线程安全、synchronized、死锁、wait/notify 全解析(含 ...

打印 上一主题 下一主题

主题 1938|帖子 1938|积分 5814

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
在 Java 并发编程中,“线程安全” 是核心议题之一。本文将深入讲解线程安全的实现手段、synchronized 的利用方式、可重入锁、死锁的成因与避免、wait/notify 通信机制等,并共同实际代码案例,帮助你彻底搞懂 Java 线程协作机制。
  
一、线程安全与加锁机制

1. synchronized 的利用方式

synchronized 是 Java 最根本的加锁工具,保证代码块在多个线程中“互斥”执行。
① 修饰普通方法(锁的是当前实例 this)

  1. public synchronized void syncMethod() {
  2.     // 线程安全的逻辑
  3. }
复制代码
② 修饰静态方法(锁的是当前类的 .class 对象)
  1. public synchronized static void staticSyncMethod() {
  2.     // 静态同步逻辑
  3. }
复制代码
③ 修饰代码块(可以机动选择锁对象)
  1. public void method() {
  2.     synchronized (this) {
  3.         // 同步逻辑
  4.     }
  5. }
复制代码

2. 锁竞争与锁冲突



  • 同一对象加锁:多个线程竞争同一把锁,会造成阻塞等待(锁冲突)。
  • 不同对象加锁:互不干扰,线程可并发执行。
  1. Runnable task = () -> {
  2.     synchronized (lockObject) {
  3.         // 临界区代码
  4.     }
  5. };
复制代码

二、可重入性:不会死锁的“重复加锁”

Java 的 synchronized 是可重入锁。也就是说,一个线程可以多次获得同一把锁,不会导致死锁。
  1. public synchronized void outer() {
  2.     inner(); // 同一线程再次进入 synchronized 方法
  3. }
  4. public synchronized void inner() {
  5.     // 安全执行
  6. }
复制代码

三、死锁问题与避免

死锁产生的典型场景

1. 两个线程两把锁,互相称待对方开释

  1. class DeadlockExample {
  2.     private final Object lock1 = new Object();
  3.     private final Object lock2 = new Object();
  4.     public void task1() {
  5.         synchronized (lock1) {
  6.             System.out.println("Task1 获得了lock1");
  7.             try { Thread.sleep(100); } catch (InterruptedException ignored) {}
  8.             synchronized (lock2) {
  9.                 System.out.println("Task1 获得了lock2");
  10.             }
  11.         }
  12.     }
  13.     public void task2() {
  14.         synchronized (lock2) {
  15.             System.out.println("Task2 获得了lock2");
  16.             try { Thread.sleep(100); } catch (InterruptedException ignored) {}
  17.             synchronized (lock1) {
  18.                 System.out.println("Task2 获得了lock1");
  19.             }
  20.         }
  21.     }
  22. }
复制代码
   这个例子满足死锁的 4 个须要条件,其中最核心的是“循环等待”。
  死锁避免战略



  • 同一加锁次序:总是先加 lock1,再加 lock2,避免循环依赖。
  • 利用 tryLock() + 超机遇制(需利用 ReentrantLock)。

四、线程通信:wait 和 notify 的正确利用方式

利用wait() 和 notify() 方法


  • wait():线程 自愿等待,进入“暂停”状态,直到被别人叫醒。
  • notify():叫醒一个正在等待的线程。
  • notifyAll():叫醒所有等待的线程(但只有一个能拿到锁继承执行)。
利用场景举例:先执行线程 t1 的一部分,再由线程 t2 接力。

  1. class Task {
  2.     private final Object lock = new Object();
  3.     private boolean ready = false;
  4.     public void part1() {
  5.         synchronized (lock) {
  6.             System.out.println("T1 正在执行前半部分任务");
  7.             try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
  8.             ready = true;
  9.             lock.notify(); // 唤醒 T2
  10.         }
  11.     }
  12.     public void part2() {
  13.         synchronized (lock) {
  14.             while (!ready) {
  15.                 try {
  16.                     lock.wait(); // 主动释放锁并阻塞
  17.                 } catch (InterruptedException ignored) {}
  18.             }
  19.             System.out.println("T2 收到通知,继续执行后续任务");
  20.         }
  21.     }
  22. }
  23. public class WaitNotifyDemo {
  24.     public static void main(String[] args) {
  25.         Task task = new Task();
  26.         Thread t1 = new Thread(task::part1);
  27.         Thread t2 = new Thread(task::part2);
  28.         t2.start(); // T2 先 wait
  29.         t1.start(); // T1 后 notify
  30.     }
  31. }
复制代码
  与 join() 和 sleep() 相比,wait/notify 更机动,支持提前叫醒和条件控制。
  
wait 的底层流程


  • 开释锁
  • 阻塞等待
  • 被叫醒后重新竞争锁
  • 重新获取锁并继承执行

notify 与 notifyAll 的区别


  • notify():随机叫醒一个正在 wait() 的线程。
  • notifyAll():叫醒所有等待线程,但只有一个能乐成获得锁。

五、volatile 与内存可见性

在多线程环境中,每个线程大概并不直接操纵主内存中的变量,而是从主内存读取变量到本身的缓存中进行操纵。这就大概出现如许的情况:


  • 一个线程修改了变量的值,但另一个线程看不到这个变化(因为仍在用旧的缓存)。
  • 导致线程间的通信出现“看不见的修改”。
这就是内存可见性问题
示例代码

  1. public class VisibilityProblem {
  2.     private static boolean running = true;
  3.     public static void main(String[] args) {
  4.         Thread t = new Thread(() -> {
  5.             while (running) {
  6.                 // 执行代码
  7.             }
  8.             System.out.println("线程停止");
  9.         });
  10.         t.start();
  11.         try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
  12.         running = false; // 主线程修改 running
  13.         System.out.println("主线程修改 running 为 false");
  14.     }
  15. }
复制代码
大概效果:

即使主线程已经把 running 改为 false,t 线程大概还一直在死循环,因为它利用的是本地缓存值而不是主内存的值。

解决方式:利用 volatile

  1. private static volatile boolean running = true;
复制代码
 一旦利用 volatile 修饰变量,修改后的值会立刻革新到主内存,并且所有线程每次访问变量时都会从主内存读取,从而保证了内存可见性。

结语

Java 多线程的本质是对“共享资源 + 并发访问”下的一种控制与协作。明白 synchronized 的利用方式、死锁的本质、以及 wait/notify 的协作机制,能有效帮助我们写出更安全、机动的并发程序。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

瑞星

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表