【JavaEE】-- 多线程(初阶)2

文章目录

  • 3.线程的状态
    • 3.1观察线程的所有状态
    • 3.2线程状态和状态转移的意义
  • 4.多线程带来的的风险-线程安全 (重点)
    • 4.1观察线程不安全
    • 4.2 线程不安全的原因
      • 4.2.1 线程调度是随机的
      • 4.2.2 修改共享数据
      • 4.2.3 原子性
      • 4.2.4 内存可见性
      • 4.2.5 指令重排序
    • 4.3解决之前的线程不安全问题
  • 5.synchronized 关键字监视器锁monitorlock
    • 5.1 synchronized的特性
      • 5.1.1 内存可见性
      • 5.1.2 如何判断多个线程竞争的是同一把锁
    • 5.2 synchronized的特性
      • 5.2.1 互斥
      • 5.2.2 可重入
      • 5.2.3 可见性
    • 5.3 synchronized使用示例
    • 5.4 Java标准库中的线程安全类

3.线程的状态

3.1观察线程的所有状态

public class Demo01_Thread {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}

输出结果:

NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
  1. NEW:表示创建好了一个Java线程对象,安排好了任务,但是还没有启动没有调用startO方法之前是不会创建PCB的,和PCB没有任何关系.
  2. RUNNABLE:运行+就绪的状态,在执行任务时最常见的状态之一,在系统中有对应PCB
  3. BLOCKED:等待锁的状态,阻塞中的一种
  4. WATING:没有等待时间,一直死等,直到被唤醒
  5. TIMEDWATING:指定了等待时间的阻塞状态,过时不侯
  6. TERMINATED:结束,完成状态,PCB已经销毁,但是JAVA线程对象还在

3.2线程状态和状态转移的意义

在这里插入图片描述
在这里插入图片描述
问:线程等待时需不需要设置等待时间?
答:不一定。要根据具体的业务需求来决定。

4.多线程带来的的风险-线程安全 (重点)

4.1观察线程不安全

public class Demo02_Thread {public static void main(String[] args) throws InterruptedException {Count count = new Count();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count{public long count = 0;public void increase(){count++;}
}

输出结果:
在这里插入图片描述
在这里插入图片描述
程序运行的结果和预期值不一样,而且是一个错误的结果,但是我们的程序逻辑是正确的,这个现象所表现的问题称为线程安全问题

4.2 线程不安全的原因

4.2.1 线程调度是随机的

由于线程是抢占式执行的,所以执行顺序是随机的。

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

4.2.2 修改共享数据

  1. 多个线程修改同一个变量,会出现线程安全问题。
  2. 多个线程修改不同的变量,不会出现线程安全问题。
  3. 一个线程修改一个变量,也不会出现线程安全问题。

4.2.3 原子性

原子性是在指令的层面上来说的。

我们Java程序中的一条count++语句,对应的是多条CPU指令。

  1. 从内存或者寄存器中读取count的值 LOAD
  2. 执行自增 ADD
  3. 把计算结果写回寄存器或者内存中 STORE

线程是抢占式执行的,所以可能有很多种执行顺序。
在这里插入图片描述
指令的执行过程

  1. 将count加载到t1中
    在这里插入图片描述
  2. 将count加载到t2中
    在这里插入图片描述
  3. t1中执行自增操作
    在这里插入图片描述
  4. t2中执行自增操作
    在这里插入图片描述
  5. 将t1中的count加载到主内存中
    在这里插入图片描述
  6. 将t2中的count加载到主内存中
    在这里插入图片描述

4.2.4 内存可见性

在这里插入图片描述

JMM规定,工作内存和线程之间是一一对应的。

JMM(Java Memory Model:Java内存模型)的规定

  1. 所有的线程不能直接修改内存中的共享变量
  2. 如果要修改共享变量,需要把这个变量从主内存中复制到自己的工作内存中,修改完成之后再刷回主内存。
  3. 各个线程之间不能互相通信,做到了内存级别的线程隔离。

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

线程对共享变量的修改,,线程之间相互感知不到。

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

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

4.2.5 指令重排序

我们写的代码在编译之后可能会与代码对应的指令顺序不同,这个过程就是指令重排序(Java层面可能会重排,CPU执行指令时也可以重排)。
指令重排序必须保证程序的运行结果是正确的,在单线程的环境中是没有任何问题的,指令重排序在逻辑上互不影响。
在这里插入图片描述

面试题
JMM的特性?
保证原子性
保证内存可见性
保证有序性(禁止指令重排序)
再去回答JMM对线程修改变量的规定
主内存–>在工作内存中修改–>刷回主内存
线程之间内存是隔离的

4.3解决之前的线程不安全问题

在这里插入图片描述

5.synchronized 关键字监视器锁monitorlock

5.1 synchronized的特性

线程A拿到了锁,别的线程如果执行被锁住的代码,必须要等到线程A释放锁如果线程A没有释放锁,那么别的线程只能阻塞等待,这个状态就是BLOCK。
先拿锁–》执行代码—》释放锁–》下一个线程再拿锁…
1. 为方法加锁

public class Demo01_Thread {public static void main(String[] args) throws InterruptedException {Count count = new Count();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count{public long count = 0;public synchronized void increase(){count++;}
}

输出结果:

count = 100000

2. 修饰代码块

public class Demo02_Thread {public static void main(String[] args) throws InterruptedException {Count02 count = new Count02();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count02 {public long count = 0;public void increase(){//真实业务中,在执行加锁的代码块之前有很罗的数据获取或其他的可以并行执行的逻辑// 1.从数据库中查询数据selectALL();// 2.对数据进行处理buiLd();// 3.其他的非修改共享变量的方法..// 当执行到修改共享变量的逻辑时,再加锁// 通过锁定代码块synchronized (this){count++;}//还有一些非修改共享变量的方法。}
}

输出结果:

count = 100000

在这里插入图片描述
synchronized只解决了原子性问题,他所修饰的代码由并行变成了串行。

5.1.1 内存可见性

在这里插入图片描述

后一个线程永远读到的是上一个线程刷回主内存的值,主内存相当于一个交换空间,线程依次写入和读取,而且是串行(顺序执行)的过程,通过这样的方式实现了内存可见性,并没有对内存可见性做技术上的处理。

synchronized实现了内存可见性。

synchronized的特性

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

总结synchronied

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

只给一个线程加锁,也会出现线程安全问题

public class Demo501_Thread {public static void main(String[] args) throws InterruptedException {Count501 count = new Count501();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase1();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count501 {public long count = 0;public synchronized void increase(){count++;}public void increase1(){count++;}
}

输出结果:

count = 82228

线程获取锁:

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

5.1.2 如何判断多个线程竞争的是同一把锁

如何描述一把锁?锁与线程之间如何关联?
锁对象,本身就是一个简单对象,任何对象都可以作为锁对象。
a. 实例对象:new出来的对象。
b. 类对象
锁对象中记录了获取到锁的线程信息(线程地址)。

在这里插入图片描述

5.2 synchronized的特性

5.2.1 互斥

一个线程获取了锁之后,其他线程必须要阻塞等待,只有当持有的线程把锁释放之后,所有线程再去竞争锁。
在这里插入图片描述

5.2.2 可重入

在这里插入图片描述
在方法的调用链路中,存在多个被synchronized修饰的方法单个线程这种情况下存不存在锁竞争?
对于同一个锁对象和同一个线程,如果可以重复加锁,称之为不互斥,称之为可重入。
对于同一个锁对象和同一个线程,如果不可以重复加锁,称之为互斥就会形成死锁。
已经获取锁对象的线程,如果再进行多次的加锁操作,不会产生互斥现象。

5.2.3 可见性

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

5.3 synchronized使用示例

  1. 执行不同对象的synchronized方法 ,修改全局变量。
public class Demo502_Thread {public static void main(String[] args) throws InterruptedException {Count502 count = new Count502();Count502 count1 = new Count502();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count502 {public static long count = 0;public synchronized void increase(){count++;}
}

输出结果:

count = 85541
  1. synchronized修饰方法时,锁对象就是当前对象。
  2. 这两个对象不是同一个实例,也意味着两个线程的锁对象 不同,不存在锁竞争关系,所以存在线程安全问题,输出结果错误。
  1. 使用单独的锁对象
public class Demo503_Thread {public static void main(String[] args) throws InterruptedException {Count503 count = new Count503();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count503 {public static long count = 0;//单独定义一个对象作为锁对象使用Object locker = new Object();public synchronized void increase(){//只锁定代码块synchronized (locker){count++;}}
}

输出结果:

count = 100000 

线程在锁竞争的时候通过locker这个对象记录线程信息。
这两个线程的锁对象都是locker,存在锁竞争关系。

  1. 在多个实例中使用单独的锁对象
public class Demo504_Thread {public static void main(String[] args) throws InterruptedException {Count504 count = new Count504();Count504 count1 = new Count504();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count504 {public static long count = 0;//单独定义一个对象作为锁对象使用Object locker = new Object();public synchronized void increase(){//只锁定代码块synchronized (locker){count++;}}
}

输出结果:

count = 98497

每个count实例中都有一个locker,两个实例的锁对象是不同的,不存在锁竞争关系。

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

输出结果:

count = 100000

locker是同一个对象,都是count中的成员变量,会产生锁竞争。

  1. 使用静态全局对象作为锁对象
public class Demo506_Thread {public static void main(String[] args) throws InterruptedException {Count506 count = new Count506();Count506 count1 = new Count506();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count1.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count506 {public static long count = 0;//单独定义一个对象作为锁对象使用// 全局变量,属于类对象static Object locker = new Object();public synchronized void increase(){//只锁定代码块synchronized (locker){count++;}}
}

输出结果:

count = 100000

静态全局变量,属于类对象,全局只有一个,在所有的实例对象之间共享,产生锁竞争。

  1. 用类对象作为锁对象
public class Demo507_Thread {public static void main(String[] args) throws InterruptedException {Count507 count = new Count507();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count507 {public static long count = 0;public synchronized void increase(){synchronized (Count507.class){count++;}}
}

输出结果:

count = 100000

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

  1. 使用String.class作为锁对象
public class Demo507_Thread {public static void main(String[] args) throws InterruptedException {Count507 count = new Count507();Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count.count);}
}class Count507 {public static long count = 0;public synchronized void increase(){synchronized (String.class){count++;}}
}

输出结果:

count = 100000

任何一个对象都可以作为锁对象,只要多个线程访问的锁对象是同一个,那么他们就存在竞争关系,否则没有竞争关系。
String.class是一个类对象,且全局唯一。

5.4 Java标准库中的线程安全类

Java 标准库中很多都是线程不安全的.这些类可能会涉及到多线程修改共享数据,⼜没有任何加锁措施.

ArrayList、 LinkedList、HashMap、TreeMap、HashSet、TreeSet、StringBuilder

但是还有⼀些是线程安全的.使⽤了⼀些锁机制来控制.

Vector(不推荐使⽤)、HashTable(不推荐使⽤)、ConcurrentHashMap、StringBuffer

还有的虽然没有加锁,但是不涉及"修改",仍然是线程安全的

String

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

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

相关文章

安卓系统远程控制电脑方法,手机远控教程,ToDesk工具

不知道大家有没有觉得手机、平板虽然很好用&#xff0c;却也仍存在有很多替代不了电脑的地方。就比如说撰写文档、做数据报表啥的就不如PC端操作般方便&#xff0c;就跟别说PS修图、AE视频剪辑等需高性能设备来带动才易用的了。 好在也是有对策可解决&#xff0c;装个ToDesk远程…

机器学习(李宏毅)——RL(强化学习)

一、前言 本文章作为学习2023年《李宏毅机器学习课程》的笔记&#xff0c;感谢台湾大学李宏毅教授的课程&#xff0c;respect&#xff01;&#xff01;&#xff01; 二、大纲 What is RL&#xff1f;Three steps in MLPolicy GradientActor-CriticReward Shaping 三、What …

【Go】Go wire 依赖注入

1. wire 简介 wire 是一个 Golang 的依赖注入框架&#xff08;类比 Spring 框架提供的依赖注入功能&#xff09; ⭐ 官方文档&#xff1a;https://github.com/google/wire 这里关乎到编程世界当中一条好用的设计原则&#xff1a;A用到了B&#xff0c;那么B一定是通过依赖注入的…

《动手学机器人学》笔记

目录 0.介绍1.概述&#xff5c;空间位置、姿态的描述&#xff08;33&#xff09;&#xff5c;《动手学机器人学》2.&#xff08;2&#xff09;-Robotics Toolbox①&#xff08;V10.4&#xff09;3.齐次坐标与变换矩阵4.一般形式的旋转变换矩阵5.&#xff08;轴角法&#xff09;…

【蓝桥杯单片机】第十三届省赛第二场

一、真题 二、模块构建 1.编写初始化函数(init.c) void Cls_Peripheral(void); 关闭led led对应的锁存器由Y4C控制关闭蜂鸣器和继电器 2.编写LED函数&#xff08;led.c&#xff09; void Led_Disp(unsigned char ucLed); 将ucLed取反的值赋给P0 开启锁存器 关闭锁存…

大语言模型基础

简介 AI大模型是“人工智能预训练大模型”的简称&#xff0c;包含了“预训练”和“大模型”两层含义&#xff0c;二者结合产生了一种新的人工智能模式&#xff0c;即模型在大规模数据集上完成了预训练后无需微调&#xff0c;或仅需要少量数据的微调&#xff0c;就能直接支撑各…

java Web

1.JavaWeb开发 前面的学习javase开发&#xff0c;而javaweb开发需要服务器和网页。 具备: java mysql jdbc htmlcssjs。 web服务器: tomcat服务器. 部署项目。 https://tomcat.apache.org/download-80.cgi 解压软件压缩包即可 不要放在中文目录和特殊符号的目录下 启动tomcat服…

SOME/IP--协议英文原文讲解12(完结)

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.3 Compa…

光明谷推出AT指令版本的蓝牙音箱SOC 开启便捷智能音频开发新体验

前言 在蓝牙音箱市场竞争日益激烈的当下&#xff0c;开发一款性能卓越且易于上手的蓝牙音箱&#xff0c;成为众多厂商追求的目标。而光明谷科技有限公司推出的 AT 指令版本的蓝牙音箱 SOC&#xff0c;无疑为行业带来了全新的解决方案&#xff0c;以其诸多独特卖点&#xff0c;迅…

STM32——HAL库开发笔记22(定时器3—呼吸灯实验)(参考来源:b站铁头山羊)

本文利用前几节所学知识来实现一个呼吸灯实验&#xff1a;两颗led灯交替呼吸。 一、STM32CubeMX配置 step1&#xff1a;配置调试接口 step2&#xff1a;配置定时器 定时器1位于APB2总线上&#xff0c;如上图所示。 step3&#xff1a;配置时基单元 按照下图配置 时钟来源配置…

医疗AI领域中GPU集群训练的关键技术与实践经验探究(下)

五、医疗 AI 中 GPU 集群架构设计 5.1 混合架构设计 5.1.1 参数服务器与 AllReduce 融合 在医疗 AI 的 GPU 集群训练中,混合架构设计将参数服务器(Parameter Server)与 AllReduce 相结合,能够充分发挥两者的优势,提升训练效率和模型性能。这种融合架构的设计核心在于根…

修改Ubuntu系统用户密码(root密码)的方法

本文介绍在Linux系统的Ubuntu电脑中&#xff0c;修改账户用户密码&#xff08;同时也修改了root用户密码&#xff09;的方法。 首先&#xff0c;如果此时处于登录页面&#xff08;也就是意识到自己忘记密码的那个页面&#xff09;&#xff0c;就先点击右上角的关闭按钮&#xf…

【清华大学】DeepSeek从入门到精通系列教程 第五版:DeepSeek与AI幻觉 pdf文档下载

【清华大学】DeepSeek使用教程系列之DeepSeek与AI幻觉 pdf文件完整版下载 https://pan.baidu.com/s/17evZMjiGNR0hun2jVdAkbg?pwd1234 提取码: 1234 或 https://pan.quark.cn/s/160d03fa907f DeepSeek与AI幻觉内容摘要 一、‌定义与类型‌ AI幻觉指模型生成与事实不符…

记录此刻:历时两月,初步实现基于FPGA的NVMe SSD固态硬盘存储控制器设计!

背景 为满足实验室横向项目需求&#xff0c;在2024年12月中下旬导师提出基于FPGA的NVMe SSD控制器研发项目。项目核心目标为&#xff1a;通过PCIe 3.0 x4接口实现单盘3000MB/s的持续读取速率。 实现过程 调研 花了半个月的时间查阅了一些使用FPGA实现NVME SSD控制器的论文、…

【Linux】进程

1. 多任务&#xff08;并发&#xff09; 让系统具备同时处理多个任务的能力。 2. 如何实现多任务 1&#xff09;进程 2&#xff09;线程 3. 进程 正在执行的程序&#xff0c;需要消耗内存和cpu&#xff0c; 一个动态执行的过程。 进程生存周期&#xff1a; …

3D模型在线转换工具:轻松实现3DM转OBJ

3D模型在线转换是一款功能强大的在线工具&#xff0c;支持多种3D模型格式的在线预览和互转。无论是工业设计、建筑设计&#xff0c;还是数字艺术领域&#xff0c;这款工具都能满足您的需求。 3DM与OBJ格式简介 3DM格式&#xff1a;3DM是一种广泛应用于三维建模的文件格式&…

Docker安装Open WebUI教程

Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离线运行。它支持各种LLM运行器,如 Ollama 和 OpenAI 兼容的 API,并内置了 RAG 推理引擎,使其成为强大的 AI 部署解决方案。 官网文档地址:https://docs.openwebui.com/ 一、拉取镜像 下载的镜像包比…

VSCode集成deepseek使用介绍(Visual Studio Code)

VSCode集成deepseek使用介绍&#xff08;Visual Studio Code&#xff09; 1. 简介 随着AI辅助编程工具的快速发展&#xff0c;VSCode作为一款轻量级、高度可扩展的代码编辑器&#xff0c;已成为开发者首选的工具之一。DeepSeek作为AI模型&#xff0c;结合Roo Code插件&#x…

京东广告基于 Apache Doris 的冷热数据分层实践

一、背景介绍 京东广告围绕Apache Doris建设广告数据存储服务&#xff0c;为广告主提供实时广告效果报表和多维数据分析服务。历经多年发展&#xff0c;积累了海量的广告数据&#xff0c;目前系统总数据容量接近1PB&#xff0c;数据行数达到18万亿行&#xff0c;日查询请求量8…

五、Three.js顶点UV坐标、纹理贴图

一部分来自1. 创建纹理贴图 | Three.js中文网 &#xff0c;一部分是自己的总结。 一、创建纹理贴图 注意&#xff1a;把一张图片贴在模型上就是纹理贴图 1、纹理加载器TextureLoader 注意&#xff1a;将图片加载到加载器中 通过纹理贴图加载器TextureLoader的load()方法加…