多线程(二)

ops/2025/3/15 19:00:40/

文章目录

    • 1.线程不安全问题
    • 2.synchronized
    • 3.volatile
    • 4.wait()方法和notify()方法

1.线程不安全问题

java">public class demo2 {public  static int count=0;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread (()->{for (int i = 0; i <50000 ; i++) {increase();}});Thread thread2 = new Thread (()->{for (int i = 0; i <50000 ; i++) {increase();}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count);}public static void increase(){count++;}
}

在这里插入图片描述
出现线程安全问题
原因:
1.线程是抢占执行的(执行顺序是随机的)
线程的执行是人为无法改变的,完全由CPU自己调度,抢占式线程是造成线程不安全的主要原因.
2.共享资源的并发性
多个线程修改同一个变量会引发线程不安全
当多个线程同时访问和修改同一个共享资源(如变量、对象、数据结构等)时,如果没有适当的同步机制,就可能导致数据不一致。
3.非原子性
某些操作(如复合操作)并不是原子性的,即它们可以被中断。例如,读取一个变量、修改它并写回的过程可能被其他线程打断,导致数据不一致。
4.可见性问题
可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。 由于 CPU 缓存的存在,线程对共享变量的修改可能不会立即刷新到主内存中,导致其他线程读取到过时的数据。
5.指令重排序
为了提高性能,编译器和处理器可能会对指令进行重排序。 指令重排序可能会导致程序的执行顺序与代码的编写顺序不一致,从而导致线程安全问题。

2.synchronized

synchronized 关键字: 这是最基本的同步机制。 它可以用于修饰方法或代码块。
同步方法: 当一个线程进入 synchronized 方法时,它会获取该方法所属对象的锁。 其他线程必须等待该线程释放锁才能进入该方法。
加上synchronized关键字此代码逻辑正确

  1. 同步方法
    使用 synchronized 修饰一个实例方法,表示该方法在同一时间只能被一个线程访问。
    没有加synchronized关键字
    在这里插入图片描述
    加上synchronized关键字
    在这里插入图片描述

  2. 同步代码块
    使用 synchronized 修饰代码块,可以指定一个对象作为锁,这样可以提高程序的并发性,因为不同的代码块可以使用不同的锁
    在这里插入图片描述

  3. 静态同步方法
    如果使用 synchronized 修饰静态方法,那么锁定的是类的 Class 对象,而不是实例对象。这意味着同一时间只有一个线程可以访问该类的所有静态同步方法。
    在这里插入图片描述
    只给一个线程加锁也会出现线程安全问题
    在这里插入图片描述
    没有出现锁竞争线程安全出现问题。
    线程获取锁:
    1.若只有一个线程A可以获取锁,不会产生锁竞争
    2.线程A与线程B不是竞争同一把锁,不会产生锁竞争
    3.线程A与线程B竞争同一把锁时,产生锁竞争,谁先抢到就执行自己逻辑,另外一线程阻塞等待,直到待持锁的完成,再参与锁竞争

synchronized特性:
1.通过锁保证了原子性
2.通过串行避免线程读取到过时的数据
3.不保证有序性(不会禁止指令重排序)

3.volatile

  1. volatile 关键字
    volatile 能保证内存可⻅性
    volatile 修饰的变量, 能够保证 “内存可⻅性”.
    比特就业课
    代码在写⼊ volatile 修饰的变量的时候,
    • 改变线程⼯作内存中volatile变量副本的值
    • 将改变后的副本的值从⼯作内存刷新到主内存
    代码在读取 volatile 修饰的变量的时候,
    • 从主内存中读取volatile变量的最新值到线程的⼯作内存中
    • 从⼯作内存中读取volatile变量的副本
java">public class demo5 {// 创建两个线程// * 1. 第一个线程,不停循环去执行自己的任务// * 2. 第二个线程,输入一个停止标识,使第一个线程退出static int flag = 0;static Object joker;public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("t1线程启动");while (flag == 0) {//不停循环,处理任务}System.out.println("t1线程结束");});t1.start();Thread t2 = new Thread(() -> {System.out.println("t2线程启动");Scanner scanner=new Scanner(System.in);System.out.println("输入一个非零整数");flag=scanner.nextInt();System.out.println("t2线程结束");});t2.start();}}

在这里插入图片描述
在这里插入图片描述
使用volatile
在这里插入图片描述
volatile特性:
1.不保证原子性
2.实现了内存可见性
3.禁止指令重排序

4.wait()方法和notify()方法

wait()⽅法
wait 做的事情:
• 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)
• 释放当前的锁
• 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁.
wait 要搭配 synchronized 来使⽤. 脱离synchronized 使⽤ wait 会直接抛出异常.
wait 结束等待的条件:
• 其他线程调⽤该对象的 notify ⽅法.
• wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
• 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常.

notify ⽅法是唤醒等待的线程.
• ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其
它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
• 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)
• 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏
完,也就是退出同步代码块之后才会释放对象锁

java">public static void main(String[] args) {// 定义一个锁对象Object locker = new Object();// 创建调用wait() 的线程Thread t1 = new Thread(() -> {while (true) {synchronized (locker) {System.out.println("调用wait()之前...");// 执行线程的逻辑// 如果没有满足线程所需要的数据,那么就等待try {locker.wait(0l);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("wait()唤醒之后..");System.out.println("===================");}}});Thread t2 = new Thread(() -> {while (true) {System.out.println("notifyAll()之前...");// 同等待的锁对象进行唤醒synchronized (locker) {locker.notifyAll();}System.out.println("notifyAll()之后...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程t1.start();t2.start();}

在这里插入图片描述
wait()与notify()必须配合synchronized使用,并且使用同一对象
在这里插入图片描述


http://www.ppmy.cn/ops/166011.html

相关文章

Linux内核实时机制18 - RT调度器1 - 数据结构

文章目录 1、Linux调度概述2、实时调度类 rt_sched_class2.1、SCHED_FIFO 调度策略2.2、SCHED_RR 调度策略3、实时调度相关数据结构3.1、实时调度实体 sched_rt_entity3.2、优先级队列rt_prio_array3.3、实时就绪队列 rt_rq3.4、带宽控制结构体 rt_bandwidth3.5、组调度结构体 …

简述下npm,cnpm,yarn和pnpm的区别,以及跟在后面的-g,--save, --save-dev代表着什么

文章目录 前言一、npm&#xff0c;cnpm&#xff0c;yarn和pnpm的基本介绍和特点1.npm (Node Package Manager)2. Yarn3. cnpm (China npm)4. pnpm 二、简述npm和pnpm 的存储方式和依赖数1.存储方式2.依赖树 三、两者依赖树的差异导致结果的对比四、简单说说-g&#xff0c;--sav…

大数据如何赋能零售行业进行产品创新

零售市场日新月异&#xff0c;品牌之间同质化严重&#xff0c;产品创新成为了品牌提升竞争力&#xff0c;实现二次增长的重要策略&#xff0c;随着时代及技术的发展&#xff0c;大数据在产品创新的应用及地位愈加重要&#xff0c;如何巧妙利用庞大的大数据&#xff0c;充分发掘…

自用testAiPlan

以下是针对蓝桥杯B组保三争二的完整30天冲刺计划&#xff0c;精确到每日任务、高频考点和突破策略&#xff0c;标注&#x1f31f;的为争二重点内容&#xff1a; 计划特点 保三基础&#xff1a;确保填空题全对 编程前3题高正确率争二突破&#xff1a;强化动态规划/BFS/数学建模…

C#-使用VisualStudio编译C#工程

一.创建csproj文件 二.创建源cs文件 三.生成解决方案 四.运行解决方案 五.VisualStudio功能列表 <1.代码格式化: CtrlKD完成代码整体格式化 <2.窗口布局 窗口->重置窗口布局 <3.引用查找&关联 <4.包管理 <5.日志输出级别 工具->选项->项目解决方案…

S7-1200 G2移植旧版本S7-1200程序的具体方法示例

S7-1200 G2移植旧版本S7-1200程序的具体方法示例 前期概要: S7-1200 G2必须基于TIA博途V20,之前的程序可通过移植的方式在新硬件上使用。 该移植工具可自动将TIA Portal 项目从 S7-1200 移植到更新的S7-1200 G2。 注意: 该插件支持在同一TIA Portal项目实例内将软件和/或硬…

嵌入式硬件: GPIO与二极管基础知识详解

1. 前言 在嵌入式系统和硬件开发中&#xff0c;GPIO&#xff08;通用输入输出&#xff09;是至关重要的控制方式&#xff0c;而二极管作为基础电子元件&#xff0c;广泛应用于信号整流、保护电路等。本文将从基础原理出发&#xff0c;深入解析GPIO的输入输出模式&#xff0c;包…

你的完美主义:从缺陷到超能力

所属专栏&#xff1a;《逻辑辨证系列》 前情回顾&#xff1a; 《完美还是完成》&#xff08;一&#xff09;&#xff1a;完成还是完美—完成大于完美 时间、机会、情绪成本 先完成 … 本期&#xff1a; 《完美还是完成》&#xff08;二&#xff09;&#xff1a;你的完美主…