线程安全(重点)
文章目录
- 一.线程安全的概念
- 1.1 线程安全的概念
- 1.2 线程不安全的原因
- 1.3 解决线程不安全
- 二.synchronized-monitor lock(监视器锁)
- 2.1 synchronized的特性
- (1)互斥
- (2)刷新内存
- (3)可重入
- 2.2 synchronied使用方法
- 1.直接修饰普通方法:
- 2.修饰静态方法:
- 3.修饰代码块:
- 三.死锁
- 3.1死锁的情况
- 3.2 死锁的四个必要条件
- 1.互斥使用
- 2.不可抢占
- 3.请求和保持
- 4.循环等待
- 3.3解决死锁的办法
- 四.volatile 关键字
- 五. wait和notify
- 5.1 wait()方法
- 5.2 notify()方法
一.线程安全的概念
先来看一段代码
class Counter{public int count = 0;public void add(){count++;}}
public class Thread14 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.add();}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {counter.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+ counter.count);}
}
可以看到结果是不确定的
1.1 线程安全的概念
先来说一下非线程安全的概念:非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。
则线程安全:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
1.2 线程不安全的原因
先解释上述线代码程不安全的原因:
如果两个线程并发执行count++,此时就相当于两组load add save进行执行,此时不同的线程调度顺序就可能会产生一些结果上的差异
由于线程的抢占执行,导致当前执行到任意一个指令,线程都可能bei调度走,CPU让别的线程来执行
如下图:
导致下面的结果:
线程安全问题的原因:
1.抢占式执行,随机调度(根本原因)
2.代码结构:多个线程同时修改同一个变量
3.原子性(操作是非原子性,容易出现问题)
4.内存可见性问题(如一个线程读,一个线程改)
5.指令重排序
1.3 解决线程不安全
从原子性入手,通过加锁,把非原子的,转成"原子"的
加了synchronized之后,进入方法就会加锁,出了方法就会解锁,如果两个线程同时尝试加锁,此时一个能获取锁成功,另一个只能阻塞等待(BLOCKED),一直阻塞到刚才的线程解锁,当前线程才能加锁成功
二.synchronized-monitor lock(监视器锁)
2.1 synchronized的特性
(1)互斥
- 进入sychronized修饰的代码块,相当于加锁
- 退出sychronizde修饰的代码块,相当于解锁
(2)刷新内存
synchronized的工作过程:
1.获得互斥锁
2.从内存拷贝变量的最新副本到工作的内存
3.执行代码
4.将更改后的共享变量的值刷新到主内存
5.释放互斥锁
(3)可重入
synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题(自己可以再次获取自己的内部锁)
理解"把自己锁死"
一个线程没有释放锁,然后又尝试再次加锁
按照之前对于锁的设定,第二次加锁的时候,就会阻塞等待,而获取不到第一次的锁,就把自己锁死
2.2 synchronied使用方法
1.直接修饰普通方法:
锁的SynchronizedDemo1对象
public class SynchronizedDemo1 {public synchronized void methond() {}
}
2.修饰静态方法:
锁SynchronizedDemo2对象
public class SynchronizedDemo2 {public synchronized void methond() {}
}
3.修饰代码块:
明确指定锁哪个对象
public class SychronizedDemo{public void method(){sychronized(this){}}
}
锁类对象
public class SynchronizedDemo {public void method() {synchronized (SynchronizedDemo.class) {}}
}
三.死锁
3.1死锁的情况
1.一个线程,连续加锁两次,如果锁是不可重入锁,就会死锁
2.两个线程,两把锁,t1和t2各自先针对锁A和锁B加锁,在获取对方的锁
public class Thread15 {public static void main(String[] args) {Object lock1 = new Object();Object lock2= new Object();Thread t1 = new Thread(()->{synchronized (lock1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2){System.out.println("t1把锁1和锁2都获得了");}}});Thread t2 = new Thread(()->{synchronized (lock2){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1){System.out.println("t2把锁1和锁2都获得了");}};});t1.start();t2.start();}
}
3.多个线程,多把锁(相当于2的一般情况)
3.2 死锁的四个必要条件
1.互斥使用
线程1拿到了锁,线程2就须等着
2.不可抢占
线程1拿到锁A之后,必须是线程1主动释放
3.请求和保持
线程1拿到锁A之后,在尝试获取锁B,A这把锁还是保持的
4.循环等待
线程1尝试获取到锁A和锁B,线程2尝试获取锁B和锁A,线程1在获取B的时候等待线程2释放B,同时线程2 在获取A的时候等待线程1释放A
3.3解决死锁的办法
给锁编号,然后指定一个固定的顺序来加锁,任意线程加把锁,都让线程遵守上述顺序,此时循环等待自然破除
对于synchronied前三个条件都是锁的基本特性,我们只能对四修改
public class Thread15 {public static void main(String[] args) {Object lock1 = new Object();Object lock2= new Object();Thread t1 = new Thread(()->{synchronized (lock1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2){System.out.println("t1把锁1和锁2都获得了");}}});Thread t2 = new Thread(()->{synchronized (lock1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2){System.out.println("t2把锁1和锁2都获得了");}};});t1.start();t2.start();}
}
四.volatile 关键字
volatile 和内存可见性问题密切相关
一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,此时读取到值,不一定是修改之后的值(归根结底是编译器/jvm在多线程下优化时产生了误判)
使用汇编语言解释
1.load,把内存中flag的值,读取到寄存器
2.cmp把寄存器的值和0进行比较,根据比较结果,决定下一不执行.
由于load执行速度太慢(相比于cmp来说),再加上反复load的结果都一样,JVM就不在重复load判定没人改flag值,就只读取一次就好
而给flag加上volatile关键字,告诉编译器变量是"易变"的,不再进行优化
class MyCounter{volatile public int flag = 0;
}
public class Thread16 {public static void main(String[] args) {MyCounter myCounter = new MyCounter();Thread t1 = new Thread(() ->{while (myCounter.flag == 0){//循环体空着}System.out.println("t1循环结束");});Thread t2 = new Thread(() ->{Scanner scanner = new Scanner(System.in);System.out.println("请输入一个整数:");myCounter.flag = scanner.nextInt();});t1.start();t2.start();}
}
结果:
五. wait和notify
wait和notify可以协调线程之间的先后顺序
完成这个协调工作, 主要涉及到三个方法
- wait() / wait(long timeout): 让当前线程进入等待状态.
- notify() / notifyAll():唤醒在当前对象上等待的线程.
注意: wait, notify, notifyAll 都是 Object 类的方法
5.1 wait()方法
wait的操作
1.先释放锁
2.在阻塞等待
3.收到通知之后,重新获取锁,并且在获取锁后,继续往下执行
wait操作需要搭配synchorized来使用
public class Thread17 {public static void main(String[] args) throws InterruptedException {Object object = new Object();System.out.println("wait之前");object.wait();System.out.println("wait之后");}
}
无synchorized的情况
wait无参数版本,就是死等
wait带参数版本,指定了等待的最大时间
5.2 notify()方法
notify()方法是唤醒等待线程
如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
notfiyAll()方法可以一次唤醒所有的等待线程
相关文章:

线程安全(重点)
文章目录一.线程安全的概念1.1 线程安全的概念1.2 线程不安全的原因1.3 解决线程不安全二.synchronized-monitor lock(监视器锁)2.1 synchronized的特性(1)互斥(2)刷新内存(3)可重入2.2 synchronied使用方法1.直接修饰普通方法:2.修饰静态方法:3.修饰代码块:三.死锁3.1死锁的情…...

软件测试面试找工作你必须知道的面试技巧(帮助超过100人成功通过面试)
目录 问题一:“请你自我介绍一下” 问题二:“谈谈你的家庭情况” 问题三:“你有什么业余爱好?” 问题四:“你最崇拜谁?” 问题五:“你的座右铭是什么?” 问题六:“谈谈你的缺点” 问题七ÿ…...

Python快速入门:类、文件操作、正则表达式
类、文件操作、正则表达式1. 类2. 文件操作3. 正则表达式1. 类 类是用来描述具有相同的属性和方法的集合,定义了该集合中每个对象共有的属性和方法,对象是类的实例,可以调用类的方法。 定义类时,如有父类,则写在类名…...

java-day01
程序就是有序指令的集合 cmd执行java程序,javac Test.java,java Test java技术平台: javaSE标准版,javaEE企业版,javaME小型版 java语言面向对象的(oop),java跨平台性的(…...

玩转 Node.js 集群
一、介绍 Node 在 v0.8 时直接引入了 cluster 模块,用以解决多核 CPU 的利用率问题,同时也提供了较完善的 API,用以处理进程的健壮性问题。 cluster 模块调用 fork 方法来创建子进程,该方法与 child_process 中的 fork 是同一个…...

Day909.MySQL 不同的自增 id 达到上限以后的行为 -MySQL实战
MySQL 不同的自增 id 达到上限以后的行为 Hi,我是阿昌,今天学习记录的是关于MySQL 不同的自增 id 达到上限以后的行为的内容。 MySQL 里有很多自增的 id,每个自增 id 都是定义了初始值,然后不停地往上加步长。 虽然自然数是没有…...

JVM学习.01 内存模型
1、前言对于C、C程序员来说,在内存管理领域,他们拥有对象的“所有权”。从对象建立到内存分配,不仅需要照顾到对象的生,还得照顾到对象的消亡。背负着每个对象生命开始到结束的维护和管理责任。对于JAVA程序来说,因为J…...

R+VIC模型应用及未来气候变化模型预测
RVIC模型融合实践技术应用及未来气候变化模型预测在气候变化问题日益严重的今天,水文模型在防洪规划,未来预测等方面发挥着不可替代的重要作用。目前,无论是工程实践或是科学研究中都存在很多著名的水文模型如SWAT/HSPF/HEC-HMS等。虽然&…...

搞懂vue 的 render 函数, 并使用
render函数是什么 简单的说,在vue中我们使用模板HTML语法组建页面的,使用render函数我们可以用js语言来构建DOM 因为vue是虚拟DOM,所以在拿到template模板时也要转译成VNode(虚拟节点)的函数,而用render函数构建DOM,vu…...

【Linux】GDB的安装与使用
安装安装gdb的具体步骤如下:1、查看当前gdb安装情况rpm -qa | grep gdb如果有,则可以先删除:rpm -e --nodeps 文件名如果没有,则进行下一步。2、下载gdb源码包或者直接apt安装。apt命令安装:sudo apt install gdb源码包…...

MySQL索引特性
文章目录为什么要有索引?认识磁盘磁盘的结构磁盘的盘片结构定位扇区磁盘随机访问 (Random Access)与连续访问 (Sequential Access)MySQL与磁盘交互索引的理解测试主键索引索引的原理索引结构是否可以使用其他数据结构B树 vs B树聚簇索引 vs 非聚簇索引为什么要有索引…...

Python 面向对象编程——类定义与对象
<类定义与对象声明> 面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥…...

基于 Apache Flink 的实时计算数据流业务引擎在京东零售的实践和落地
摘要:本文整理自京东零售-技术研发与数据中心张颖&闫莉刚在 ApacheCon Asia 2022 的分享。内容主要包括五个方面: 京东零售实时计算的现状实时计算框架场景优化:TopN场景优化:动线分析场景优化:FLINK 一站式机器学…...

【JavaEE】如何将JavaWeb项目部署到Linux云服务器?
写在前面 大家好,我是黄小黄。不久前,我们基于 servlet 和 jdbc 完善了博客系统。本文将以该系统为例,演示如何将博客系统部署到 Linux 云服务器。 博客系统传送门: 【JavaEE】前后端分离实现博客系统(页面构建&#…...

Mysql常用命令
mysql连接: [roothost]# mysql -u root -p Enter password:******创建数据库: CREATE DATABASE 数据库名; 删除数据库: drop database 数据库名; 使用mysqladmin删除数据库: [roothost]# mysqladmin -u root -p dr…...

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(4)
目录 写在前面: 题目:P1149 [NOIP2008 提高组] 火柴棒等式 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述: 输入格式: 输出格式: 输入样例: 输出样例: 解题思路: …...

在Win10以及SDK为33的环境下——小米便签项目的搭建
文章目录0. 我的操作系统和开发环境1. 相关文件下载:2. import project:2.1 用import project导入项目3. make project:3.1 AS中的命令行乱码问题:3.2 依赖库缺失问题:3.3 关于targetSdkVersion3.4 关于Missing URL3.5 关于Manifest merger f…...

FPGA纯verilog实现RIFFA的PCIE通信,提供工程源码和软件驱动
目录1、前言2、RIFFA简介RIFFA概述RIFFA架构RIFFA驱动3、vivado工程详解4、上板调试验证并演示5、福利:工程代码的获取1、前言 PCIE是目前速率很高的外部板卡与CPU通信的方案之一,广泛应用于电脑主板与外部板卡的通讯,PCIE协议极其复杂&…...

Linux网络配置
文章目录一、Linux网络配置原理图二、查看网络IP和网关ping测试主机之间网络连通性三、linux网络环境配置第一种方法(自动获取)第二种方法(指定ip)四、设置主机名和hosts映射设置主机名设置hosts映射五、主机名解析过程分析(Hosts、DNS)Hosts是什么DNS一、Linux网络配置原理图 …...

【Java学习笔记】多线程与线程池
多线程与线程池一、多线程安全与应用1、程序、进程与线程的关系2、创建多线程的三种方式(1)继承Thread类创建线程【不推荐】(2)实现Runnable接口创建线程(3)Callable接口创建线程3、线程的生命周期4、初识线…...

尺取法
尺取法是一种线性的高效率算法。记 (L, R ) 为一个序列内以L为起点的最短合法区间, 如果R随L的增大而增大的,就可以使用尺取法。具体的做法是不断的枚举 L,同时求出R。 因为 R 随 L增大而增大,所以总时间复杂度为 O(n) 指针i、j的两种方向: 反向扫描:i、j方向相反,i从头…...

20.有效的括号
给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括…...

使用QT C++编写一个带有菜单和工具条的文本编辑器
您好,这是必应。我可以帮您生成一段使用QT C编写一个带有菜单和工具条的文本编辑器的代码,但是请注意,这只是我的创造性的输出,并不代表任何权威或专业的观点。如果您想要了解更多的相关知识,请自行搜索或咨询专家。以…...

文法和语言的基本知识
一、什么形式化的方法用一套带有严格规定的符号体系来描述问题的方法二、什么是非形式化的方法对程序设计语言的描述从语法、语义和语用三个方面因素来考虑所谓语法是对语言结构定义所谓语义是描述了语言的含义所谓语用则是从使用的角度去描述语言三、符号串字母表和符号串字母…...

学习其他人的代码,成为更好的程序员
学习其他人的代码,成为更好的程序员1. 广泛阅读2. 分析代码3. 记笔记4. 实验5. 分享你的发现6. 结论参考如何成为一名更好的Python程序员??? 学习编码是一个持续的过程,需要实践、实验和向他人学习的意愿。提高编码技能的最佳方法之一是学习他人的代…...

新星计划-JAVA学习路线及书籍推荐
CSDN的各位友友们你们好,今天千泽为大家带来的是JAVA学习路线及其经典书籍推荐,接下来让我们一起了解一下JAVA的学习路线吧!如果对您有帮助的话希望能够得到您的支持和关注,我会持续更新的! 目录 1.JAVASE及其书籍推荐 2.初级数据结构与算法及其书籍推荐 3.MySQL及其书籍推荐…...

【大数据】Hive系列之- Hive-DML 数据操作
Hive系列-DML 数据操作数据导入向表中装载数据(Load)语法操作用例通过查询语句向表中插入数据(Insert)创建一张表插入数据基本模式插入(根据单张表查询结果)查询语句中创建表并加载数据(As Sele…...

day2 —— 判断字符串中的字符是否唯一
目录 前言 问题描述 代码解释 前言 若是想要了解基本语法的话,请到(7条消息) C语言从练气期到渡劫期_要一杯卡布奇诺的博客-CSDN博客查看相应的语法细节 强烈安利这篇文章 —— (4条消息) 筑基五层 —— 位运算看这篇就行了_要一杯卡布奇诺的博客-CSDN博客 问题…...

176万,GPT-4发布了,如何查看OpenAI的下载量?
大家好,这里是程序员晚枫。 昨天新一代GPT4发布了,今年GPT不断给大家带来惊喜。 在OpenAI的官网,也公开了GPT的Python调用第三方库:openai。 今天我们就来看看,这个Python智能接口~ 1、代码说明 开发过Python项目…...

蓝蓝算法题(一)
讲在前面:1.本人正在逐步学习C,代码中难免有C和C(向下兼容)混用情况。2.算法题目来自蓝蓝知识星球,没有对应的判决系统,运行到判决系统可以会有部分案例不能通过。 求素数 暴力求解(1 - n试探…...