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

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.xdnf.cn/news/1060345.html

相关文章:

  • 介绍一款免费MES、开源MES系统、MES源码
  • uni.getStorage 与 uni.getStorageSync 的区别解析
  • 矩阵变换终极笔记
  • react forwardRef和readux的connect冲突,导致ref.current获取不到值
  • infinisynapse 使用清华源有问题的暂时解决方法:换回阿里云源并安装配置PPA
  • 【MySQL基础】MySQL内置函数全面解析:提升你的数据库操作效率
  • AWK在网络安全中的高效应用:从日志分析到威胁狩猎
  • 苍穹外卖-2025 完成基础配置环节(详细图解)
  • 【嵌入式硬件实例】-555定时器控制舵机/伺服电机
  • 力扣网C语言编程题:接雨水(动态规划实现)
  • SCRM软件数据分析功能使用指南:从数据挖掘到商业决策
  • 什么是Nacos
  • TDengine 集群超能力:超越 InfluxDB 的水平扩展与开源优势
  • jquery 赋值时不触发change事件解决——仙盟创梦IDE
  • repo 工具
  • 动态规划笔记
  • FastMCP框架进行MCP开发:(一)基础环境搭建及测试
  • 云XR(AR/VR)算力底座关键特征与技术路径
  • 颈部不自主偏移现象解析
  • systemverilog中关于多线程的若干思考
  • SAP LPD(launchpad)配置使用手册
  • C#学习13——正则表达式
  • 计算机网络学习笔记:TCP可靠传输实现、超时重传时间选择
  • leetcode 2294. 划分数组使最大差为 K 中等
  • Kernel K-means:让K-means在非线性空间“大显身手”
  • 机器学习×第十二卷:回归树与剪枝策略——她剪去多余的分支,只保留想靠近你的那一层
  • Arduino Nano 33 BLE Sense Rev 2开发板使用指南之【环境搭建 / 点灯】
  • 基于微信小程序和深度学习的宠物照片拍摄指导平台的设计与实现
  • 【AI编程】第3期,针对AI生成的改枪码列表创建对应的数据库表
  • 主成分分析(PCA)例题——给定协方差矩阵