当前位置: 首页 > news >正文

哈尔滨网站建设培训班技术网站模版

哈尔滨网站建设培训班,技术网站模版,诸暨市住房和建设局网站,河南省电力工程建设企业协会网站一.线程安全的定义 线程安全是指在多线程环境下,对共享资源进行并发访问时,程序能够正确地处理,不会出现数据不一致、逻辑错误等问题,确保程序的执行结果与单线程环境下的执行结果相同,或者符合预期的并发逻辑。 有些代…

一.线程安全的定义

线程安全是指在多线程环境下,对共享资源进行并发访问时,程序能够正确地处理,不会出现数据不一致、逻辑错误等问题,确保程序的执行结果与单线程环境下的执行结果相同,或者符合预期的并发逻辑。

有些代码在多线程环境执行下会出现问题,这样的问题就称为线程不安全

二.synchronized 关键字

1.作用

核心特性

  • 互斥性:同一时间只有一个线程持有锁,其他线程阻塞等待。
  • 自动释放锁:退出同步块或方法时,锁自动释放。

synchronized 关键字在 Java 里用于实现同步机制,保证同一时刻只有一个线程可以访问被保护的代码块或方法。 

2.用法

1.修饰方法  2.修饰代码块 (必须指定锁对象!!)

// 同步方法
public synchronized void syncMethod() {// 操作共享资源
}// 同步代码块
public void syncBlock() {synchronized (lockObject) {// 操作共享资源}
}

三.什么是锁对象

1. synchronized 修饰静态方法

Counter 类的 increase 方法被 static synchronized 修饰,其锁对象是 Counter 类的 Class 对象,也就是 Counter.class。示例代码如下:

1.1示例代码

class Counter {// 将 count 定义为静态变量private static int count = 0;// 使用 synchronized 保证线程安全public static synchronized void increase() {count++;}public static int getCount() {return count;}
}class Demo {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {Counter.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {Counter.increase();}});thread1.start();thread2.start();// 等待两个线程执行完毕thread1.join();thread2.join();// 打印计数器的结果System.out.println("最终计数结果: " + Counter.getCount());}
}

1.2 代码结果 

 代码结果:(因为所持有的锁对象为同一个) 

1.3 代码解释 

具体到你的代码,Counter 类的 increase 方法:

在多线程环境下,当一个线程进入 increase 方法时,它会尝试获取 Counter.class 这个锁对象。如果该锁对象当前没有被其他线程持有,那么这个线程就可以对这个锁对象进行加锁--执行 count++ 操作;在这个线程执行期间,其他线程如果也想进入 increase 方法,就会被阻塞,直到持有锁的线程执行完 increase 方法并释放 Counter.class 锁(对Counter.class这个锁对象进行解锁)。 

2. synchronized 修饰实例方法

若 synchronized 修饰的是实例方法,锁对象是调用该方法的实例对象(即 this)。示例如下:

2.1 示例代码

class Counter {public int count = 0;public synchronized void increase() {count++;}
}class Demo14 {private static Counter counter1 = new Counter();private static Counter counter2 = new Counter();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter2.increase();}});t1.start();t2.start();t1.join();t2.join();// 修正输出,打印已定义的变量System.out.println("counter1 count: " + counter1.count);System.out.println("counter2 count: " + counter2.count);}
}

2.2 代码结果 

代码结果:(各自计数为 50000

2.3 代码解释 

此时的两个线程持有的锁对象不是同一个。线程t1,t2持有的锁对象是counter1,counter2变量指向的不同Counter对象

3. synchronized 修饰代码块

这里的this表示谁调用了increase()方法里面,synchroized修饰的代码块就针对谁进行加锁

此时的两个线程的锁对象为同一个——>counter变量指向的Counter对象

题外话:

static 修饰引用对象变量。首先,static 修饰的变量属于类,所有实例共享。示例里的 Counter counter 被 static 修饰,是类变量,整个类共享这一个实例

class Counter {public int count = 0;public void increase() {synchronized (this) {count++;}}
}class Demo14 {private static Counter counter = new Counter();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();// 主线程等待子线程执行完毕t1.join(); t2.join(); System.out.println("输出结果:" + counter.count);}
}

综上所述,锁对象的选择取决于 synchronized 的使用方式,不同的锁对象会影响同步的范围和效果。

四.原子性问题

先来看一段问题代码

1. 问题代码

class Counter {// 将 count 定义为静态变量private static int count = 0;//increase定义为静态方法public static void increase() {count++;}public static int getCount() {return count;}
}class Demo {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {Counter.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {Counter.increase();}});thread1.start();thread2.start();// 等待两个线程执行完毕thread1.join();thread2.join();// 打印计数器的结果System.out.println("最终计数结果: " + Counter.getCount());}
}

1.1 运行结果

( 每次运行结果都不一样)

2.出现问题的原因

    接下来说明什么是原子性 

    2.1 什么是原子性?

    原子性指的是一个操作是不可中断的,要么全部执行,要么都不执行。在多线程环境下,如果多个线程同时修改共享变量,可能会导致数据不一致。比如 i++ 这样的操作,虽然看起来是一条语句,但实际上分为读取、增加和写入三个步骤,这就不是原子的。

    原子性问题源于 CPU 指令的非原子性。例如,i++ 看似是一条语句,但实际分为三步:

    1. 读取:从内存读取变量 i 的值。
    2. 增加:在 CPU 寄存*器中执行 +1 操作。
    3. 写入:将结果写回内存。

    2.2 问题代码解释


    count++ 操作并非原子操作,其底层执行分为 “读取值 → 计算新值 → 写入值” 三个步骤。多线程环境下,若多个线程同时执行 count++,可能出现以下场景:

    1. 线程 A 读取 count 值为 10,还未执行写入;
    2. 线程 B 也读取 count 值为 10(此时线程 A 的修改未生效);
    3. 线程 A、B 分别计算新值为 11 并写入。最终 count 只增加了 1,而非预期的增加 2,导致计数丢失。

    3.解决方案

    跳转到———目录1.二.三

    五.内存可见性问题

    • 每个线程有自己的工作内存(缓存),共享变量存储在主内存中。
    • 线程操作变量时需先将变量从主内存复制到工作内存,修改后再写回主内存。
    • 示例:线程 A 修改主内存中的变量 x,若未及时写回,线程 B 的工作内存中仍保留旧值 x=0,导致可见性问题。

    1. 问题代码 

    public class VisibilityProblem {private static boolean flag = false;public static void main(String[] args) throws InterruptedException {// 修改线程:500ms后修改flag为truenew Thread(() -> {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}flag = true;System.out.println("修改线程:flag已设为true");}).start();// 读取线程:循环检查flagnew Thread(() -> {while (!flag) {// 空循环,等待flag变为true}System.out.println("读取线程:接收到flag为true");}).start();}
    }

    1.1 预期输出

    修改线程:flag已设为true

    读取线程:接收到flag为true

    1.2 实际输出

    修改线程:flag已设为true (程序不终止,读取线程无法感知flag的修改)

    2. 问题原因

    • 读取线程的工作内存(缓存)读取的是 flag 的旧值(false),未从主内存更新,导致循环无法终止

    3.解决办法

    1.使用volatile 关键字

    原理

    • volatile 强制读取线程每次从主内存获取 flag 的最新值,确保可见性。

    修改后的代码 

     private static volatile boolean flag = false; // 添加volatile关键字

     2.使用 synchronized

    原理

    • synchronized 保证同一时间只有一个线程操作 flag,且退出同步块时强制将 flag 写回主内存,确保可见性。

    修改后的代码

    public class VisibilityProblem {private static boolean flag = false;// 静态锁对象(所有线程共享同一把锁)private static final Object LOCK = new Object();public static void main(String[] args) throws InterruptedException {// 修改线程:500ms后修改flag为truenew Thread(() -> {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (LOCK) { // 获取锁flag = true;System.out.println("修改线程:flag已设为true");}}).start();// 读取线程:循环检查flagnew Thread(() -> {while (true) { // 死循环+锁内检查synchronized (LOCK) { // 获取同一把锁if (flag) { // 锁内读取,保证可见性System.out.println("读取线程:接收到flag为true");break; // 退出循环}}// 锁外可添加短暂休眠避免空转(非必须)// Thread.yield();}}).start();}
    }

     3.两者区别

    • 简单场景用 volatile,保证可见性。但不保证原子性
    • 复杂场景用 synchronized 或 Lock,同时保证原子性和可见性。

    六.抢占式执行问题

    1.作用

    waitnotifyObject类的两个重要方法,用于实现线程间的通信与协作(调度线程执行顺序),它们通常和synchronized关键字配合使用

     2.wait和notify和notifyAll

    1.wait():使当前线程进入等待状态,同时释放该线程持有的对象锁。线程会进入等待队列 ,直到其他线程调用同一对象的notify()notifyAll()方法将其唤醒

    2.notify()唤醒等待在同一对象上的一个线程。被唤醒的线程不会立即执行,而是进入阻塞队列,等待调用notify()的线程释放锁之后,再重新竞争锁,获取到锁后才能继续执行。

    3.notifyAll()唤醒等待在同一对象上的所有线程。这些被唤醒的线程同样会进入阻塞队列竞争锁。通常情况下,为避免某些线程长时间处于等待状态导致死锁,推荐使用notifyAll()

    .使用注意事项 

    • 必须在同步块或同步方法中调用waitnotify方法必须在synchronized修饰的代码块或方法中使用。因为它们依赖于对象的监视器锁(monitor),只有持有该对象锁的线程才能调用这两个方法,否则会抛出IllegalMonitorStateException异常。
    • 调用线程需持有对象锁:当线程调用wait方法时,它必须已经持有该对象的锁;同样,调用notifynotifyAll方法的线程也需要持有对象锁。

    3. 极简案例:

    《两个线程交替打印数字和字母(5 轮)》:

    public class SimpleAlternatePrint {private static final Object LOCK = new Object();private static int turn = 0; // 0=数字线程,1=字母线程public static void main(String[] args) {// 数字线程(打印1-5)new Thread(() -> {for (int i = 1; i <= 5; i++) {synchronized (LOCK) {while (turn != 0) { // 不是自己的回合,等待try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); }}System.out.print(i + " "); // 打印数字turn = 1; // 切换回合LOCK.notify(); // 唤醒字母线程}}}).start();// 字母线程(打印A-E)new Thread(() -> {for (char c = 'A'; c <= 'E'; c++) {synchronized (LOCK) {while (turn != 1) { // 不是自己的回合,等待try { LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); }}System.out.print(c + " "); // 打印字母turn = 0; // 切换回合LOCK.notify(); // 唤醒数字线程}}}).start();}
    }

    4.复杂案例

    《三个线程按顺序执行任务》:

    class ThreadSequence {private boolean isFirstDone = false;private boolean isSecondDone = false;// 第一个线程执行的方法public synchronized void firstTask() throws InterruptedException {System.out.println("First task started");// 模拟任务执行Thread.sleep(1000);System.out.println("First task completed");//把isFirstDone标志为true,第二个线程结束等待循环了,开始进入下一任务isFirstDone = true;notifyAll();// 1.唤醒等待在同一个对象上的所有线程 //2.第一个线程释放锁}// 第二个线程执行的方法public synchronized void secondTask() throws InterruptedException {while (!isFirstDone) {// 等待第一个线程完成wait();}System.out.println("Second task started");// 模拟任务执行Thread.sleep(1000);System.out.println("Second task completed");isSecondDone = true;// 唤醒等待的第三个线程notifyAll();}// 第三个线程执行的方法public synchronized void thirdTask() throws InterruptedException {while (!isSecondDone) {// 等待第二个线程完成wait();}System.out.println("Third task started");// 模拟任务执行Thread.sleep(1000);System.out.println("Third task completed");}
    }

    4.1 运行结果

    4.2 运行顺序说明 

    1. First 线程执行 firstTask

    • 持有锁 → 打印First task started → 睡眠 1 秒 → 打印First task completed → 设置isFirstDone=true → 调用notifyAll()

    • 关键:唤醒所有等待在ThreadSequence对象上的线程(包括 Second、Third 线程),但此时 Second/Third 尚未进入等待状态(因未获取锁)。

    2. Second 线程执行 secondTask

    • 竞争锁 → 进入循环:while (!isFirstDone) → 第一次检查isFirstDone=false → 调用wait() → 释放锁,进入等待队列

    • 等待:直到 First 线程调用notifyAll()后,Second 线程被唤醒 → 重新竞争锁 → 检查isFirstDone=true → 退出循环

    • 执行:打印Second task started → 睡眠 1 秒 → 打印Second task completed → 设置isSecondDone=true → 调用notifyAll()

    3. Third 线程执行 thirdTask

    • 竞争锁 → 进入循环:while (!isSecondDone) → 第一次检查isSecondDone=false → 调用wait() → 释放锁,进入等待队列

    • 等待:直到 Second 线程调用notifyAll()后,Third 线程被唤醒 → 重新竞争锁 → 检查isSecondDone=true → 退出循环

    • 执行:打印Third task started → 睡眠 1 秒 → 打印Third task completed

    http://www.dtcms.com/a/533905.html

    相关文章:

  1. 网站服务器和直播服务器一样吗深圳网站设计官网
  2. 大型网站如何做别名夏津建设局网站
  3. 20-Java-面向对象-static
  4. 株洲网站建设优化网站建设方案书含合同
  5. 网站页面设计稿做网站构架
  6. 2019/12 JLPT听力原文 问题四
  7. 宁海县城镇建设局网站怎么做论坛社区网站
  8. 垡头做网站的公司室内设计接单网站
  9. 郑州区块链数字钱包网站开发过程西宁设计网站
  10. WordPress全站展示建网站一般多少钱
  11. 自己做的网站发布详细步骤深圳网站设计报价
  12. 做咖啡网站网站域名过期怎么办
  13. 车机系统资源性能测试
  14. 宁波免费网站建站模板软文营销的技巧有哪些?
  15. 商务网站建设的流程网站网站是怎么建设的
  16. 网站建设与管理书宁波网站建设运营
  17. 网站建设教育自己开网站怎么开
  18. phpcms v9网站上传石景山企业网站建设
  19. 【系统分析师】高分论文:论信息系统开发方法及应用(电子商务门户网站系统)
  20. HTML02 核心语法与基础标签:构建静态页面的基石
  21. TPC-C是一个在线事务处理(OLTP)基准测试标准
  22. Ackermann(阿克曼)函数
  23. 常见的手机网站湖北网站设计公司
  24. 企业建站系统下载做网站推广的联系方式
  25. 各种通信(五):IIC协议(二)
  26. 做gif网站用模板怎么做网站
  27. 【Java面试笔记:多线程】44、深入Java线程池:BlockingQueue实现全景解析与实战指南
  28. 网站建设怎么找到客户做传销网站违法的吗
  29. 船山网站建设排版漂亮的网站
  30. 网站域名有什么用中国优化网