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

多线程2(Thread)

线程的状态

1.NEW:线程创建出来了,但是还是没有被start。

创建好Thread后,先使用getState方法查看状态,再start,看到的就是NEW。

2.TERMINATED :线程执行结束,入口方法执行结束,但是Thread对象还在。

线程结束后调用getstate,观察到的线程状态就是terminated。

3.RUNNABLE :线程执行过程中一直是此状态。

在线程start和join之间创建就可以看到RUNNABLE状态。

4.WAITING,TAME_WAITING:这两个状态都是阻塞状态,WAITING是无时间限制的阻塞状态;

TAME_WAITING是有时间限制的阻塞状态。

我们可以通过jconsole观察。

5.BLOCKED : 通过“加锁”产生的阻塞。

线程安全问题(重点)

通俗的讲就是,一段代码在单线程情况下运行没事,但是在多线程下就会产生BUG。

出现线程安全的原因

1.(根本原因)是操作系统随机调度的问题(抢占式执行),

2.多个线程在修改同一个变量时,就会产生线程安全问题(多个线程读同一个变量没事),例如:

产生这样的原因就涉及到“原子性问题”,像count++这样的操作,站在CPU的角度上来看,就是三条指令,当单线程执行时,CUP就会将这三条指令按顺序一条一条执行,但是当多线程执行时,由于操作系统调度的随机性,可能在这一条线程执行到一半时,另一条线程将加工到一半的变量拿走,这样就会导致结果错误。

3.修改操作,不是原子的:

像事务中的原子性就是将多条sql语句打包成一个“原子”,在线程安全问题中,像count++这样的操作,就涉及到多条CPU指令,本质上CPU指令层面不是原子的,是会拆分成多个指令的。

但在Java中对内置类型和引用类型赋值(=)就是原子的。

4.内存可见性

解决线程安全问题(synchronized)

解决线程安全的方法是通过加锁实现,将要执行的代码打包成一个整体,从而达到原子性这样的效果。

加锁是通过synchronized这个关键字。

进入synchronized中就是加锁,离开synchronized(){}就是解锁,synchronized进行加锁需要一个锁对象,这个对象只能是引用类型,不能是内置类型。

synchronized关键字执行原理

synchronized并不是将count++中的三个指令都打包成一个指令,也不是在CPU上一口气全部执行完。

而是加锁的这个线程会影响到别的线程,而且是同一个线程。

例如:当一个线程在执行任务时,当另一个线程想要执行同一个任务时,此时就会触发阻塞,等到第一个线程执行完了再由第二个线程执行。

一个线程加锁而另一个线程不加锁就不会触发阻塞;两个线程同时加同一把锁才会产生锁竞争,有阻塞的效果。

synchronized常见用法

1.将一段代码块进行加锁

2.修饰一个普通方法,就可以省略锁对象。

这个加锁相当于对this加锁,和单独使用一个锁对象进行加锁没有区别。

3.修饰一个静态方法,这里是针对类对象进行加锁。

synchronized的可重入

如果针对同一把锁连续加两次。

上述代码当第一个synchronized执行后,开始执行第二个synchronized,而第二的所需的锁对象和第一个一样,而第一个synchronized还没执行完,此时就会在第二个synchronized处产生阻塞,而产生阻塞就会让第一个synchronized无法继续执行,这样就产生了死锁(deadlock)。

但是上述的代码在Java中不会触发死锁(并不是Java中没有死锁,死锁有很多种形式),是因为Java中有“可重入”这样的性质。

可重入锁:一个线程,针对同一把锁,连续加锁多次,不会触发死锁,这样的锁就叫可重入锁。

可重入锁只能解决死锁中的其中一种情况:一个线程两把锁。

死锁的几种情况

1.一个线程两把锁:在Java中引入了可重入这样的性质,所以这种情况在Java中不会触发死锁。

2.两个线程两把锁:

上述代码就产生了死锁,这是因为第一个线程和第二个线程都拿了各自的锁,当一方想要调用另一方的锁时,但双方的锁对象还没有释放,此时就会产生阻塞,因而产生死锁。

3.N个线程M个锁

如何避免死锁

构成死锁的四个必要条件:

1.锁是互斥的

2.锁不可被抢占:线程1拿到这个锁,线程2也要拿这把锁,此时线程2就会触发等待,而不是直接抢占。

3.请求和保持:拿到一把锁的情况下,不释放,再去请求第二把锁。

4.循环等待:等待关系,与其他线程等待关系构成循环。

解决方法:

1.打破请求和保持,将第一把锁释放,再去申请第二把锁。

2.通过规定使用锁的顺序,避免等待关系构成循环。

内存可见性引发的线程安全问题(volatile)

上述代码当线程2修改了flag的值后,线程1并没有检测到线程2的修改,这就是内存可见性引发的问题。

内存可见性问题其实是由编译器自身的优化产生,就以上面的例子:

编译器会从内存中读到寄存器中,再从寄存器中读取,但是编译器多次读到的数据都是一样的,而这样的开销比较大,所以编译器就不会再去内存中读取,而是在寄存器中读,这样我们修改的数据就无法被编译器读取到,自然产生问题。

大多数情况下,编译器优化可以做到“逻辑不变”,但是在特殊情况下会出现误判,导致逻辑发生变化,特别是“多线程代码”,这是因为线程调用是随机的。

此时就要使用Java中的一个关键字:volatile

当加上volatile之后,我们修改的值就会被编译器识别到。

当然如果要执行的任务比较大,编译器就不会进行例如上面的优化。

但是volatile只在内存可见性问题中起作用,在其他线程安全中不涉及,volatile和synchornized是两个不一样的维度。

相关文章:

  • Python爬虫伪装
  • 【JJ斗地主-注册安全分析报告】
  • 单例模式与锁(死锁)
  • 爆炸仿真的学习日志
  • 在 Caliper 中执行不同合约的方法
  • ComfyUI 文生图教程,进行第一次的图片生成
  • AI 模型分类全解:特性与选择指南
  • 【配置 YOLOX 用于按目录分类的图片数据集】
  • Flask 核心概念速览:路由、请求、响应与蓝图
  • 如何轻松、安全地管理密码(新手指南)
  • MySQL基础(五)事务、DCL权限控制、视图、同义词、索引及练习
  • 动手学深度学习12.7. 参数服务器-笔记练习(PyTorch)
  • 绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
  • 使用 Windows 完成 iOS 应用上架:Appuploader对比其他证书与上传方案
  • Linux驱动学习day2
  • 图纸外发审核审批管控,筑牢企业数据安全防线
  • uniapp 开发ios, xcode 提交app store connect 和 testflight内测
  • 自动化提示生成框架(AutoPrompt)
  • 浏览器后台服务 vs 在线教育:QPS、并发模型与架构剖析
  • 【ubuntu】虚拟机安装配置,sh脚本自动化,包含 apt+时间同步+docker+mysql+redis+pgsql
  • 个人定制网站怎么做/汕头网站建设方案优化
  • 优化seo技术/惠州seo排名
  • 有没有做租赁的网站/厦门网络关键词排名
  • 大型商家进驻网站开发/今日新闻10条简短
  • 工商部门在线咨询/长安网站优化公司
  • 可上传多个视频的网站建设/搜索关键词排行榜