JavaEE->多线程2

目录

一、线程安全(重点)

1.线程安全演示

2.线程不安全的原因

1.线程是抢占式执行的(执行顺序是随机的)

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

3.原子性

4.内存可见性

5.指令重排序(有序性)

二、解决线程不安全的问题

1.锁的概念

2.synchronized

3.synchronized的特性

4.关于synchronized

5.使用单独的锁对象

6.synchronized使用示例

7.synchronized - 监视器锁monitor lock

7.1synchronized的特性

1.互斥

2.可重入

3.可见性

8.volatile 关键字


一、线程安全(重点)

1.线程安全演示

/*** 线程安全演示*/public class Text03 {public static void main(String[] args) throws InterruptedException {// 初始化累加对象Counter counter = new Counter();// 创建两个线程对一个变量进时累加// 线程1Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 线程2Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 启动线程t1.start();t2.start();// 等待线程完成t1.join();t2.join();// 查看运行结果System.out.println("count = " + counter.count);}
}// 专门用来累加的类
class Counter {// 初始值是0public int count = 0;/*** 累加方法*/public void increase () {count++;}
}
//count = 68419

程序运行结果与预期值不一致,而且是一个错误的结果,而且我们的逻辑是正确的,这个现象所表现的问题称为线程安全问题

2.线程不安全的原因

1.线程是抢占式执行的(执行顺序是随机的)

由于线程地执行顺序无法人为控制,抢占式执行是造成线程安全问题的主要原因,而且我i们解决不了,完全是CPU自己调度,而且和CPU内核数有关

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

多个线程修改同一个变量,出现线程安全问题
多个线程修改不同变量,不会出行线程安全问题
一个线程修改一个变量,不会出现线程安全问题

3.原子性

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

写的count++对应多条CPU指令
1.从内存或寄存器中读取count的值     LOAD
2.执行自增                                          ADD
3.把计算结果写回寄存器中                 STORE

CPU执行指令,和代码没关系

由于执行CPU指令不是原子性的,导致这三条指令没有执行完就被CPU调度走了
另外的线程加载到一个原始值
当两个线程分别自增完成后,把值写回内存时发生覆盖现象

4.内存可见性

1.Java线程首先是从主内存读取变量的值到自己工作内存
2.每个线程都有自己的工作内存,且工作内存间是隔离的
3.线程在自己的工作内存中把自己的值修改完成之后再把修改后的值写回主内存

以上执行的count++操作,由于是两个线程在在执行,每个线程都有自己的工作内存,且相互之间不可见,最终导致了线程安全问题

工作内存与线程之间是一一对应的(这是JVM规定的)

外存(磁盘)-->内存(运行过程被加载到内存)--> 寄存器(封装到CPU中)

工作内存是JAVA层面对物理层面的关于程序所使用的到了寄存器的抽象

 如果通过某种方式让线程之间可以相互通信,称之为内存可见性

5.指令重排序(有序性)

 我们写的代码在编译之后可能会与代码对应的指令执行顺序不同,这个过程就是指令重排序

JVM层面可能会重排, CPU执行指令时也可以重排

指令重排序必须要保证程序运行的结果是正确的  单线程的环境里是没有任何问题的 指令重排序在逻辑上互不影响

二、解决线程不安全的问题

事务的隔离级别是通过锁和MVCC机制保证的

1.锁的概念

线程A拿到了锁,别的线程如果执行被锁住的代码,必须要等到线程A释放锁,如果线程A没有释放锁,那么别的线程只能阻塞等待,这个状态就是BLOCK

先拿锁 --> 执行代码 --> 释放锁 --> 下一个线程再拿锁...

2.synchronized

可以为方法加锁也    可以为代码加锁

只解决原子性问题,它所修改的代码有并行变成了串行

public class Text01 {public static void main(String[] args) throws InterruptedException {// 初始化累加对象Counter counter = new Counter();// 创建两个线程对一个变量进时累加// 线程1Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 线程2Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 启动线程t1.start();t2.start();// 等待线程完成t1.join();t2.join();// 查看运行结果System.out.println("count = " + counter.count);}
}// 专门用来累加的类
class Counter {// 初始值是0public int count = 0;/*** 累加方法*/public synchronized void increase () {count++;}
}
//count = 100000

t1先获得了锁,执行方法, 方法执行完成之后其它线程在获取锁 
这样的情况是一个单线程运行状态
是把多线程转成了单线程,从而解决线程安全问题

解决方法单线程的执行问题,可以修改代码块  把对共享变量的修改加锁执行 

由于线程在执行逻辑之前要拿到锁,当拿到锁时,上一个线程已经执行完了所有的指令,并把修改的值刷回了主内存,当前线程读到了永远都是上一个线程修改后的值

t1释放锁之后,也有可能第二次循环时t1先于t2拿到锁,因为线程时抢占式执行的

3.synchronized的特性

1.保证了原子性(通过加锁来实现)
2.保证了内存有序性(通过串行执行实现)
3.不保证有序性

4.关于synchronized

1.被synchronized修饰的代码会变成串行执行
2.synchronized可以修饰方法,也可以修饰代码块
3.被synchronized修饰的代码并不是一次性在CPU执行完,而是中途可能被CPU调度走,当所有指令执行完成之后才会释放锁
4.只给一个线程加锁,也会出现线程不安全问题

public class Text01 {public static void main(String[] args) throws InterruptedException {// 初始化累加对象Counter01 counter = new Counter01();// 创建两个线程对一个变量进时累加// 线程1Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});// 线程2Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase1();}});// 启动线程t1.start();t2.start();// 等待线程完成t1.join();t2.join();// 查看运行结果System.out.println("count = " + counter.count);}
}// 专门用来累加的类
class Counter01 {// 初始值是0public int count = 0;/*** 累加方法*/public synchronized void increase () {count++;}public void increase1 () {count++;}
}
//count = 81072

线程获取锁:
1.如果只有与一个线程A,那么直接可以获取锁,没有锁竞争
2.线程A,B共同抢一把锁的是时候,存在锁竞争,谁先拿到就先执行自己的逻辑,另一个线程阻塞等待,等到持有锁的线程释放所之后,再参与竞争锁
3.线程A,B竞争的不是同一把所的时候,他们没有竞争关系

5.使用单独的锁对象

public class Text02 {public static void main(String[] args) throws InterruptedException {Counter02  counter = new Counter02();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter02 {// 初始值为0public static int count = 0;// 单独定义一个对象作为锁对象用Object locker = new Object();/*** 累加方法**/public void increase () {// 只定义锁代码块synchronized (locker) {count++;}}
}
// count : 100000

Counter中有一个locker,每创建一个counter都会初始化一个对象内部的成员变量locker

/*** 在多个实例中在使用锁对象*/public class Text03 {public static void main(String[] args) throws InterruptedException {Counter03  counter1 = new Counter03();Counter03  counter2 = new Counter03();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter2.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter1.count);}
}class Counter03 {// 初始值为0public static int count = 0;// 单独定义一个对象作为锁对象用Object locker = new Object();/*** 累加方法**/public void increase () {// 只定义锁代码块synchronized (locker) {count++;}}
}
//count:95487

每个counter中都有一个locker 两个线程的锁对象是不同的,不存在锁竞争关系

/*** 单个实例中,创建两个方法,使用同一个锁对象*/public class Text04 {public static void main(String[] args) throws InterruptedException {Counter04  counter = new Counter04();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase1();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter04 {// 初始值为0public static int count = 0;// 单独定义一个对象作为锁对象用Object locker = new Object();/*** 累加方法**/public void increase () {// 只定义锁代码块synchronized (locker) {count++;}}public void increase1 () {// 只定义锁代码块synchronized (locker) {count++;}}
}
//count:100000

locker是同一个对象,会产生锁竞争关系

/*** 使用静态全局变量*/
public class Text05 {public static void main(String[] args) throws InterruptedException {Counter05  counter = new Counter05();Counter05  counter1 = new Counter05();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter05 {// 初始值为0public static int count = 0;// 全局变量,属于类对象static Object locker = new Object();/*** 累加方法**/public void increase () {// 只定义锁代码块synchronized (locker) {count++;}}
}
//count:100000

类对象是全局唯一,产生锁竞争

public class Text06 {public static void main(String[] args) throws InterruptedException {Counter05  counter = new Counter05();Counter05  counter1 = new Counter05();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter06 {// 初始值为0public static int count = 0;/*** 累加方法**/public void increase () {// 只定义锁代码块synchronized (Counter06.class) {count++;}}
}
//count:100000
public class Text07 {public static void main(String[] args) throws InterruptedException {Counter05  counter = new Counter05();Counter05  counter1 = new Counter05();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count:" + counter.count);}
}class Counter07 {// 初始值为0public static int count = 0;/*** 累加方法**/public void increase () {// 只定义锁代码块synchronized (String.class) {count++;}}
}
//count:100000

任何一个对象都可以作为锁对象

只能多个线程访问的锁对象是同一个,那么他们就存在竞争关系,否则就没有竞争关系

6.synchronized使用示例

7.synchronized - 监视器锁monitor lock

7.1synchronized的特性

1.互斥

一个线程获取了锁之后,其他线程必须要阻塞等待
只有当持有锁的线程把锁释放了之后,所有的线程再去竞争锁

2.可重入
package demo3;public class Text1 {public static void main(String[] args) throws InterruptedException {Counter1 counter1 = new Counter1();Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter1.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {counter1.increase();}});t1.start();t2.start();t1.join();;t2.join();System.out.println("count = " + counter1.count);}
}class Counter1 {public static int count = 0;/*** 累加方法*/public synchronized void increase () {increase1();}private synchronized void increase1() {increase2();}private void increase2() {synchronized (this) {count++;}}
}
// count = 10000

3.可见性

通过结果来看达到内存可见性的目的,但是是通过原子性来实现的

8.volatile 关键字

package demo3;import java.util.Scanner;/*** 创建两个线程* 1. 第一个线程, 不停的执行自己的任务* 2. 第二个线程,输入一个停止标识,使第一个线程退出*/
public class Text2 {// 退出标识static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "线程启动...");while (flag == 0) {// 不停的去循环, 处理任务}System.out.println(Thread.currentThread().getName() + "线程退出...");}, "t1");// 启动线程t1.start();// 输入停止标识Thread t2 = 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");// 启动线程t2.start();}
}
/*
t2'线程启动...
t1线程启动...
请输入一个整数:>
1
t2线程退出...
*/

t2线程正常结束,并且修改了flag变量的值 
但是t1线程没有结束,整个进程页没有结束
结果不及预期,线程安全问题产生

package demo3;import java.util.Scanner;/*** 创建两个线程* 1. 第一个线程, 不停的执行自己的任务* 2. 第二个线程,输入一个停止标识,使第一个线程退出*/
public class Text2 {// 退出标识static volatile int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName() + "线程启动...");while (flag == 0) {// 不停的去循环, 处理任务}System.out.println(Thread.currentThread().getName() + "线程退出...");}, "t1");// 启动线程t1.start();// 输入停止标识Thread t2 = 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");// 启动线程t2.start();}
}
/*
t2'线程启动...
t1线程启动...
请输入一个整数:>
1
t2线程退出...
t1线程退出...
*/

解决了内存可见性
解决了有序性
不保证原子性

多个线程之间涉及的共享变量,如果只存在修改的逻辑,只管加volatile

面试题:JMM如何实现原子性,可见性,有序性

synchronized实现了原子性,由于是串行从而也实现了可见性
volatile真正实现了内存可见性,有序性(使用了内存屏障)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/87490.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/87490.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter TCP通信

启动TCP服务 Future<void> startServer() async {final server await ServerSocket.bind(InternetAddress.anyIPv4, 12345);print(Server listening on ${server.address}:${server.port});server.listen((Socket socket) {print(Client connected: ${socket.remoteAddr…

flask拆分计划

两个启动链接&#xff0c;看日志提示是因为2次启动&#xff0c;一次是database&#xff0c;一次是xmind2&#xff0c;去掉一次就可以&#xff0c;如何去掉一次&#xff1f; 这里启动也调用了一次&#xff0c;所以测试环境注释掉&#xff0c;如下图&#xff0c;也就调用了一次

【生活】ECMO原理、作用、费用及使用方法

博客目录 一、ECMO 是什么&#xff1f;二、ECMO 的作用1. 替代肺功能&#xff08;氧合与二氧化碳清除&#xff09;2. 替代心脏功能&#xff08;循环支持&#xff09;3. 为其他治疗争取时间4. 用于心肺复苏&#xff08;ECPR&#xff09; 三、ECMO 的费用1. 设备使用费2. 耗材费用…

Profinet转EtherCAT网关模块怎么用:案例分享

在某制造工厂西门子S7-1200 PLC中&#xff0c;存在一个技术难题&#xff0c;即伺服驱动器与可编程逻辑控制器&#xff08;PLC&#xff09;之间的通讯不兼容问题。具体而言&#xff0c;PLC采用的是PROFINET通讯协议&#xff0c;而伺服EtherCAT协议驱动器则需要EtherCAT协议进行数…

什么是 NLP-NLP基础知识体系的系统认知

NLP基础知识体系的系统认知 一、引言 今天的学习内容集中于自然语言处理&#xff08;NLP&#xff09;的基本概念、发展历程、核心任务及文本表示技术。通过这一学习过程&#xff0c;我对NLP这门学科有了更加系统和深入的认识&#xff0c;并且理解了NLP技术的广泛应用及其复杂…

数据结构 学习 链表 2025年6月14日08点01分

单向链表: 线性数据结构 由一系列节点组成 每个节点包含: 数据部分:存储实际数据 指针部分:储存指向下一个节点的引用 特点1,每个节点只有一个指向下一个节点的指针 特点2,只能从头到尾 单向遍历 特点3,不需要连续的内存空间 特点4,插入和删除效率高 特点5,随机访问 效率低 …

使用 Kubernetes 部署 PHP 留言板应用(含 Redis 架构)

使用 Kubernetes 部署 PHP 留言板应用&#xff08;含 Redis 架构&#xff09; 文章目录 使用 Kubernetes 部署 PHP 留言板应用&#xff08;含 Redis 架构&#xff09;教程概述技术架构特点 准备工作环境要求 Redis 数据库部署Redis 主从架构原理创建 Redis 领导者 Deployment部…

MATLAB提供的两种画误差矩阵的函数

MATLAB在统计学和机器学习工具包中提供了两种画误差矩阵&#xff08;Confusion matrix&#xff09;的函数。 figure; plotconfusion(YValidation,YPred)figure; cm confusionchart(YValidation,YPred) cm.Title Confusion Matrix for Validation Data; cm.RowSummary row-n…

【Java学习笔记】泛型

泛型 一、泛型的引出 代码示例 public class pra {public static void main(String[] args) {ArrayList arrayList new ArrayList();arrayList.add("java");arrayList.add("jack");arrayList.add("jom");arrayList.add(new a());for (Object…

SpringMVC系列(一)(介绍,简单应用以及路径位置通配符)

0 引言 作者正在学习SpringMVC相关内容&#xff0c;学到了一些知识&#xff0c;希望分享给需要短时间想要了解SpringMVC的读者朋友们&#xff0c;想用通俗的语言讲述其中的知识&#xff0c;希望与诸位共勉&#xff0c;共同进步&#xff01; 1 SpringMVC介绍 SpringMVC本质上…

Java中如何使用lambda表达式分类groupby

Java中如何使用lambda表达式分类groupby Java中如何使用lambda表达式分类groupby分类问题场景传统手写方式lambda使用groupBy()方法一行结束&#xff01;&#xff01;&#xff01;完整代码 Java中如何使用lambda表达式分类groupby 分类问题场景 比如一群学生根据性别和年龄排…

无人机开发分享——无人机集群基于braft实现长机动态推选算法

在无人机集群项目的算法开发中&#xff0c;推选长机作为集群的动态中心&#xff0c;往往承担着集群管理、通讯中继等重要功能。由于通讯链路的有限性和任务的实时性需要&#xff0c;需要保证动态长机时刻工作正常&#xff0c;并在异常情况下快速切换新长机。 本文主要分享基于b…

python 解码 jwt

import base64 import jsondef base64url_decode(base64url_data):# 将URL安全的base64编码数据转换为标准的base64编码数据base64_data base64url_data.replace(-, ).replace(_, /)# 如果数据长度不是4的倍数&#xff0c;则补齐padding_length 4 - len(base64_data) % 4base…

腾讯云TCCA认证考试报名 - TDSQL数据库交付运维工程师(MySQL版)

数据库交付运维工程师-腾讯云TDSQL(MySQL版)认证 适合人群&#xff1a; 适合从事TDSQL(MySQL版)交付、初级运维、售前咨询以及TDSQL相关项目的管理人员。 认证考试 单选*40道多选*20道 成绩查询 70分及以上通过认证&#xff0c;官网个人中心->认证考试 查询 考试费用&am…

Spring Boot的Security安全控制——认识SpringSecurity!

Spring Boot的Security安全控制 在Web项目开发中&#xff0c;安全控制是非常重要的&#xff0c;不同的人配置不同的权限&#xff0c;这样的系统才安全。最常见的权限框架有Shiro和Spring Security。Shiro偏向于权限控制&#xff0c;而Spring Security能实现权限控制和安全控制…

深入理解ArrayList:从Java原生实现到手写一个ArrayList

Java原生ArrayList解析 基本结构 Java的ArrayList是基于数组实现的动态列表&#xff0c;主要特点包括&#xff1a; 动态扩容&#xff1a;当元素数量超过当前容量时&#xff0c;自动扩容&#xff08;通常增加50%&#xff09; 快速随机访问&#xff1a;通过索引访问元素的时间…

【力扣 简单 C】206. 反转链表

目录 题目 解法一&#xff1a;迭代 解法二&#xff1a;递归 题目 解法一&#xff1a;迭代 struct ListNode* reverse(struct ListNode* head) {struct ListNode* retHead NULL;while (head){struct ListNode* nextNode head->next;head->next retHead;retHead he…

明代大模型:智能重构下的文明再发现

引言&#xff1a;当紫禁城遇见生成式AI 一幅动态的《紫禁城图卷》正通过全息投影技术演绎永乐年间的宫廷盛景。这个虚实交融的场景&#xff0c;恰似明代大模型技术的隐喻——以人工智能为纽带&#xff0c;连接起永乐盛世的恢弘气象与数字时代的文明重构。作为人工智能与历史学…

推荐使用的Unity插件(行为树Behavior )

在 Unity 6.0 中使用 Behavior Designer 行为树插件开发 AI 系统&#xff0c;需结合其核心节点设计、变量管理和代码控制。以下是详细指南&#xff0c;整合了最新版本的最佳实践&#xff1a; &#x1f6e0;️ 1. 安装与基础配置 安装插件 通过 Unity Asset Store 安装 “Behav…

107. Java 继承 - 总结:方法重写与隐藏

文章目录 107. Java 继承 - 总结&#xff1a;方法重写与隐藏**详细解释&#xff1a;****方法重载** **总结** 107. Java 继承 - 总结&#xff1a;方法重写与隐藏 在 Java 中&#xff0c;定义与超类中的方法具有相同签名的方法时&#xff0c;不同类型的方法之间会有不同的行为。…