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

线程状态线程安全

1.线程状态

2.线程安全

当我们程序没错但是运行的结果却和预想的不一致时,就叫做线程不安全,(所以,有时候并不是我们的错,曾经拥有就很好了,让我们交给命运吧)

public static void main(String[] args) throws InterruptedException {counter counter = new counter();Thread th1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.increase();}});Thread th2 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.increase();}});th1.start();th2.start();//等待执行完成th1.join();th2.join();System.out.println("count = " + counter.count);}
public class counter {int count = 0;public void increase(){count++;}
}

3.线程不安全的原因

1.线程调度是随机的

线程在cpu上是抢占式执行的,人为干预不了,是线程安全的主要原因,并且也和CPU的核数相关

2.修改共享数据(多个线程改变了同一个变量)

上面示例就是T1和T2都改变了count

3.线程执行过程中不能保证原子性

要么全部执行要么全部不执行

我们最后想要得到的结果是对count变量进行累加操作,但是count++语句会被分为多个指令,而CPU是随机执行的,所以线程是被抢占式执行,会出现T1没执行完就开始执行T2的操作,导致累加的count被覆盖的情况

4.内存不可见

Java内存模型(JMM):Java虚拟机规范中定义了Java内存模型. ⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果

简单来说就是各个线程之间感知不到各个内存的改变,就会可能造成主内存不及时的改变

5.指令重排序

我们写的代码在编译过程中可能不会按照代码的指令逻辑运行(JVM运行可能会重排,CPU执行指令也会重排)

 指令重排序需要代码之间不会造成影响

4.JMM



5.解决线程安全问题 

所以我们要解决线程安全的问题本质上需要解决原子性,内存可见性,指令重排序其中一个即可

synchronized 

本质上是让线程并行转化为线程串行,通过给线程加锁,来不让其他线程抢先执行。

通过给线程加锁,如果这个线程还没有执行完,别的线程就不会运行,如果这个线程运行完了,就对其解锁,让其他线程运行并加锁

1.实现了原子性

2.间接实现了内存可见性(通过原子性实现了线程的串行,从而实现了内存可见性)

3.未实现有序性

1.使用方法:可以给方法加上前缀,也可以写成代码块

2.与CPU调度的区别

注意:由于cpu的调度问题,当运行t1时,可能因为cpu的随即调度,去运行其他线程,但是由于t1已经上了锁,别的线程就会发生阻塞等待,所以又会调度回来,直到t1运行完为止,所以其实不是一直运行t1而是有一个过程

3.使用synchronized必须要保证是同一个锁对象

4.锁对象

锁对象记录了锁的信息,所有对象都能被定义为锁对象

锁对象内部有四个组成

1.markword:用来记录锁对象的状态

2.类型指针:当前对象的类型

3.实例数据:成员变量

4.对齐填充:一个对象所占的内存必须是8的整数倍,负责对齐

5.判断是不是锁对象 

6.锁竞争

必须要保证竞争的是同一把锁才是锁竞争

7.synchronized关键字-监视器锁-monitor  lock

1.互斥:一个线程获取锁之后,其他线程必须阻塞等待,直到这个线程执行完,释放锁,再给其他锁进行竞争

2.可重入

 

 volatile关键字

volatile主要解决的是当我们想要用一个线程去影响到另一个线程时,避免出现线程安全问题

1.不能保证原子性

2.保证了内存可读性

3.保证了有序性

 当我们想要通过改变 变量的值来实现影响别的线程时,要给此变量加上volatile

static int flag = 0;public static void main(String[] args) {Thread th1 = new Thread(()->{System.out.println(Thread.currentThread().getName() + "线程启动");while(flag == 0){//不断循环}System.out.println(Thread.currentThread().getName() + "线程结束");},"t1");th1.start();//然后我们希望通过th2来改变flag由此来影响th1Thread th2 = new Thread(()->{System.out.println(Thread.currentThread().getName() + "线程启动");Scanner scanner = new Scanner(System.in);System.out.println("请输入一个非零整数: ");flag = scanner.nextInt();System.out.println(Thread.currentThread().getName() + "线程结束");},"t2");th2.start();}

 此时th1不会如愿收到影响

 当在定义flag的地方改成static volatile int flag = 0就没问题了

分析问题产生的原因

volatile的底层原理

cpu层面

MESI缓存一致性原则

从上面我们可以得知,造成th2没有对th1造成改变的原因是th1的缓存没有和主内存保持一致性,从而造成了th1未停下来

而MESI缓存一致性原则就是保证了当某一处理器改变了主内存的值时,通知其他处理器重新改变缓存的值

Java层面

内存屏障:保证了内存的可读性,和有序性

会在volatile定义的变量的前后加上内存屏障,从而就手动制定了读和写的具体位置,保证了有序性

也可以理解为告诉了编译器,不要进行指令重排序

6.wait和notify 

等待和通知功能,wait负责通知其他处理器,notify就是处理好了之后返回告知wait,使其走下一步


1.wait和notify必须与synchronized一起使用

2.notify相当于通知功能,能无限次调用

3.wait和notify是object的方法,join和sleep等是Thread的类方法

http://www.xdnf.cn/news/16009.html

相关文章:

  • gradle微服务依赖模版
  • 软件反调试(5)- 基于注册表实时调试器检测
  • [Python] -项目实战7- 用Python和Tkinter做一个图形界面小游戏
  • 我的世界-推理
  • 基于Event Sourcing和CQRS的微服务架构设计与实战
  • 连接语言大模型(LLM)服务进行对话
  • 随着GPT-5测试中泄露OpenAI 预计将很快发布 揭秘GPT-5冲击波:OpenAI如何颠覆AI战场,碾压谷歌和Claude?
  • [硬件电路-58]:根据电子元器件的控制信号的类型分为:电平控制型和脉冲控制型两大类。
  • 威力导演 12:革新级影音创作平台——专业特效与极致效率的完美融合
  • 算法题(176):three states
  • 100个GEO基因表达芯片或转录组数据处理27 GSE83456
  • [simdjson] 实现不同CPU调度 | 自动硬件适配的抽象
  • JAVA面试宝典 -《API设计:RESTful 与 GraphQL 对比实践》
  • Linux操作系统之线程(四):线程控制
  • RabbitMQ核心组件浅析:从Producer到Consumer
  • 【Django】DRF API版本和解析器
  • ubuntu-linux-pycharm-社区版安装与django配置
  • 高性能熔断限流实现:Spring Cloud Gateway 在电商系统的实战优化
  • Linux网上邻居局域网络共享工具Samba及Smb协议,smbd,nmbd服务,smbpasswd,pdbedit命令,笔记250720
  • 数组算法之【合并两个有序数组】
  • 无线通信相关概念
  • 【机器学习深度学习】魔塔社区模型后缀全解析:Base、Chat、Instruct、Bit、Distill背后的技术密码
  • 【Elasticsearch】冷热集群架构
  • 力扣 hot100 Day50
  • 在Ubuntu22系统上离线部署ai-infra-guard教程【亲测成功】
  • windows C#-本地函数
  • 【计算机组成原理】原码、补码和移码
  • ZooKeeper学习专栏(一):分布式协调的核心基石
  • 阶段1--Linux中的计划任务
  • 大模型词表设计与作用解析