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

Java多线程(锁的操作)

线程不安全的原因

首先我们要先了解线程不安全的原因

1)线程在系统中是随机调度的,抢占式执行的

2)多个线程同时修改同一个变量

3)线程针对变量的修改操作,不是“原子”的

4)内存可见性问题,引起的线程不安全

5)指令重排序,引起的线程不安全

关于锁,主要操作

是两个方面

1)加锁 t加上锁之后,t1也尝试加锁,就会阻塞等待(都是系统内核控制)

2)解锁 直到t解锁了之后,t1才有可能拿到锁(加锁成功)

锁的主要特性:互斥

一个线程获取到锁之后,另一个线程也尝试加这个锁,就会阻塞等待(也叫做锁竞争/锁冲突)

3)代码中,可以创建出多个锁

只有多个线程竞争同一把锁,才会产生互斥,针对不同的锁

关键字synchronized

synchronized后面带上(),里面写的就是“锁对象”

{锁对象的用途。有且只有一个,就是用来区分,两个线程是否针对同一个对象加锁,如果是,就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待.如果不是,就不会出现锁竞争,也就不会阻塞等待}

4)synchronized下面跟着{}

当进入到代码块,就是给上述()锁对象进行了加锁操作,当出了代码块,就是给上述{}锁对象进行了解锁操作

5)修饰普通方法,相当于针对this加锁

修饰静态方法,相当于针对类对象加锁

package Thread;
class Count{private static int count=0;void add(){count++;}int get(){return count;}}
public class Demo12 {public static void main(String[] args) throws InterruptedException {Count count =new Count();Count count1=new Count();Thread t=new Thread(() ->{synchronized (count1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}synchronized (count) {System.out.println("t获得了两把锁");}});Thread t1=new Thread(() ->{synchronized(count) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}synchronized (count1) {System.out.println("t1获得了两把锁");}});t.start();t1.start();t.join();t1.join();}
}

死锁:有两把锁,一把锁把一把锁,锁在了里面,要想获取到第二把锁,就需要执行完第一层大括号,要想执行完第一层大括号,就需要先获取到第二层的锁,这种情况就叫做死锁

死锁有三种比较典型的场景:

场景一:

锁是不可重入锁,并且一个线程针对一个锁对象,连续加锁两次,通过引入可重入锁

场景二:

两个线程两把锁{有线程一和线程二,以及有锁A和锁B,现在线程1和2都需要获取到锁A和锁B,拿到锁A之后,不释放A,继续获取锁B}

场景三:

N个线程,M把锁{哲学家就餐问题(5个人5根筷子)}

如何避免死锁问题?

1)锁具有互斥特性(基本特点,一个线程拿到锁之后,其他线程就得阻塞等待)

2)锁不可抢占(不可被剥夺)一个线程拿到锁之后,除非他自己主动释放锁,否则别人抢不走

3)请求和保持,一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取其他锁

4)循环等待。多个线程获取多个锁的过程中,出现了循环等待,A等待B,B又等待A

必要条件:缺一不可,任何一个死锁的场景,都必须同时具备上述4点,只要缺少一个,都不会构成死锁

内存可见性引起的线程的不安全问题

package Thread;import java.util.Scanner;public class Demo13 {private volatile static int count=0;public static void main(String[] args) {Scanner scanner=new Scanner(System.in);Thread t1=new Thread(() ->{while (count==0) {}System.out.println("t1结束了");});Thread t2=new Thread(() ->{System.out.println("请输入一个数字");count = scanner.nextInt();});t1.start();t2.start();}
}

while(count ==0){}这个代码有两部过程

1)load从内存读取数据到cpu寄存器(load速度非常慢)执行一次load消耗的时间,顶几千次,上万次cmp执行的时间

2)cmp(比较。同时会产生跳转)

条件成立,继续顺序执行

条件不成立,就跳转到另外一个地址来执行 

于是jvm就把上述的load操作优化掉了,只是第一次真正的进行load,后续再执行到对应的代码,就不再真正load,而是直接读取刚才load过的寄存器的值了。

如果存在IO操作,IO操作是不能被优化掉,IO执行的时间会更慢,就没有优化load操作的必要了

上述问题的本质上还是编译器引起的,优化掉load操作之后,使t2线程的修改,没有被t1线程感知到”内存可见性“问题

如何解决就需要用到volatile关键字

加上这个关键字,编译器就知道这个变量是“反复无常的”

volatile操作和之前synchronized保证原子性,没有任何关系,volatile是专门针对内存可见性的和场景来解决问题的,并不能解决之前,两个线程循环count++的问题


http://www.mrgr.cn/news/68756.html

相关文章:

  • 【分享】这篇教程助力你成为 JavaScript 糕手!(四)
  • [MySQL]DQL语句(二)
  • 【含开题报告+文档+源码】基于SpringBoot+Vue智能居民健康检测系统设计与实现
  • FlexRay介绍
  • 风华高科签约实在RPA,引领粤港澳大湾区制造业数字化腾飞
  • 工业相机常用功能之白平衡及C++代码分享
  • IO作业day4
  • 发布一个npm组件库包
  • 哈哈,这可是“加长版”吐槽,我先声明,绝对有趣但绝对善意的深度吐槽!你要是真的看完
  • 算法训练(leetcode)二刷第二十天 | 93. 复原 IP 地址、78. 子集、90. 子集 II
  • 标准遗传算法-c++源程序
  • 从0开始学习机器学习--Day19--学习曲线
  • Moment.js、Day.js、Miment,日期时间库怎么选?
  • leetcode hot100【LeetCode 17.电话号码的字母组合】java实现
  • 快速开发工具 Vite
  • 大模型微调技术 --> IA3
  • LeetCode 每日一题 长度为 K 的子数组的能量值
  • 牛客小白月赛104-D小红开锁-模拟
  • c++:stack,queue,priority_queue模拟实现
  • 软件设计师中级 第9章 数据库技术基础
  • 从零开始学习python 7(持续更新ing)
  • 有趣的Midjourney作品赏析(附提示词)
  • Leetcode 长度最小的子数组
  • 06 Oracle性能优化秘籍:AWR、ASH、SQL trace与实时监控的实战指南
  • git基础操作
  • Python的函数