java并发编程之基础与原理1

news2024/9/24 15:15:25

java多线程基础 

 下面说一下线程的7种状态

下面我重点来说一下阻塞状态

阻塞状态是可以分很多种的:

下面用另外一张图来说明这种状态

 简单说一下线程的启动原理

下面说一下java中的线程

 

java线程的异步请求方式

上面就会先把main执行出来,等阻塞结束之后把run()方法里面的come in执行出来,这个是一个异步的操作

从线程中取得一个返回值

 1.用一个类去实现Callable接口

 上面可以返回一个结果

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

// 实现Callable接口,指定返回值类型为String
public class CallableDemo implements Callable<String> {

    // 实现call方法,该方法内为具体的任务逻辑
    @Override
    public String call() throws Exception {
        System.out.println("come in"); // 输出提示信息
        Thread.sleep(10000); // 休眠10秒,模拟耗时操作
        return "SUCCESS"; // 返回任务执行结果
    }

    // 主函数
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个固定大小为1的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        // 创建CallableDemo实例
        CallableDemo callableDemo = new CallableDemo();
        // 提交任务给线程池,获得一个Future对象
        Future<String> future = executorService.submit(callableDemo);
        System.out.println(future.get()); // 阻塞,等待任务执行完毕并获取结果
        executorService.shutdown(); // 关闭线程池
    }
}

我们把上面的线程交给线程池去执行,然后返回一个Future对象接收返回值,里面说一下future.get()方法是一个阻塞方法,比如上面线程阻塞了10秒钟,我们get()这个方法是拿不到结果的

interrupt()的作用

上面的运行情况下面分析一下:

        在某些情况下,在线程中断之前,可能不会打印消息“Test:1”。这是因为线程调度程序决定何时在线程之间切换,并且新线程可能在有机会打印消息之前就被中断了。start()方法和interrupt()方法之间的执行顺序没有保证,因此中断可能在线程有机会开始执行其run()方法之前发生。

下面我们来看另外一段代码

package com.pxx.interrupt;

public class InterruptDemo2 implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(200);//这里睡了0.2秒
                System.out.println("interrupt线程运行了");
            } catch (InterruptedException e) {//这里会有复位的作用,又把isInterrupted变为false
                System.out.println("异常被捕获");
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new InterruptDemo2());
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();//把isInterrupted()这个方法变为true
        System.out.println(Thread.currentThread().getName() + "执行了");
    }

}

  我们来看其中的一个运行结果:

那么我们来分析一下运行结果

我先来说一下InterruptedException这个异常会在什么时候去触发,它的触发条件是

        !Thread.currentThread().isInterrupted()这个位置变为true的时候,它就会去触发这个异常

并且又把这个异常复位为false

上面就是main主线程调用了start之后,就被t1线程抢走了并开始执行,然后中间main线程抢回了执行的时间片,把isInterrupted变为了true,这个时候t1线程又抢走了时间片,发现iisInterrupted()这个方法变为了true,于是触发了异常,并且复位了isInterrupted()变为了false,然后开始执行catch里面的代码,执行完一句之后,又被main线程抢走了,这个时候main执行完最后一条语句结束,然后t1去执行打印异常栈追踪信息,又开始一直循环执行里面的代码,因为没有在让isInterrupted()继续中断的条件

好了我们可以改动一下上面的代码让它继续中断

说一下并发与并行 

并发是并行的假象,看似程序在同时执行多个操作,而并发只是要求程序假装同时执行多个操作,也就是每个时间片执行一个操作,多个操作快速切换执行而已

大体说一下并发编程的三大特性 

可见性 

当一个线程修改了共享变量的值,其他线程能够看到修改的值。

具体原理分析如下:

当我们在Java程序中定义了一个变量,例如 int x = 5;,这个变量实际上在计算机内存中有两个位置保存着它的值:一个是主内存(主存,RAM),另一个是线程的工作内存(缓存,CPU缓存)。在不同的线程中,可能会有各自的工作内存。

当一个线程修改了这个变量的值,例如 x = 10;,这个修改首先会发生在线程的工作内存中,而不是直接在主内存中。其他线程如果要读取这个变量的值,通常会从主内存中读取。问题在于,由于工作内存的存在,不同的线程可能在各自的工作内存中保存了不同的变量值。

为了确保多个线程之间对共享变量的修改能够正确地被其他线程看到,Java 内存模型使用了“主内存同步”的机制。当一个线程修改了变量的值后,会将新值同步回主内存,而其他线程在读取这个变量的值之前,会先从主内存中刷新(获取)变量的最新值,而不是直接从自己的工作内存中读取。

下面说的直白一点就是:

 

有序性

 

简单说一下什么叫指令重排

指令重排是指在程序执行过程中,CPU或者编译器为了提高性能,可能会对指令的执行顺序进行优化,使得程序在逻辑上的执行顺序与实际的指令执行顺序不一致。

在Java中,由于Java代码最终会被编译成字节码,然后由JVM执行,JVM为了提高程序执行的效率,也可能会进行指令重排。这就意味着,即使程序的源代码中顺序是有序的,JVM在执行时可能会重新排列指令的执行顺序。

指令重排可能会导致多线程程序出现问题。例如,如果一个线程在初始化一个对象时,先为对象分配内存空间,然后初始化对象的属性,最后将对象的引用赋值给某个变量。如果发生了指令重排,可能会导致另一个线程在获取到对象的引用之后,访问到的对象并未完成初始化,从而引发错误。

 

 原子性

        一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行

来说一下JMM内存模型

Java内存模型(Java Memory Model,JMM)

首先要明白JMM内存模型他不是一个具体硬件存在,它是一个虚拟的概念。

了解了上面这个概念之后,我们必须去了解一下计算机内部的重要组成部分以及jvm到底占据计算机什么位置和内存是如何划分的

直接用一张图来说明一下

好了,在回过头来说JMM,它是一个概念,一个数据的处理规则对吧,下面我们就来剖析一下这个规则

JMM规范了Java虚拟机与计算机内存是如何协同工作的:

规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。它是围绕原子性,有序性,可见性展开的

单纯这样来看,我们能看懂个屁啊,既然是围绕可见性展开的,那我们就贴一段会有可见性问题的代码来说一下

package com.pxx.visibility;

public class VisibilityTest {

    private boolean flag = true;

    public void refresh() {
        flag = false;
        System.out.println(Thread.currentThread().getName() + "修改flag");
    }

    public void load() {
        System.out.println(Thread.currentThread().getName() + "开始执行...");
        int i = 0;
        //进行循环,flag = true
        while (flag) {
            i++;
        }
        System.out.println(Thread.currentThread().getName() + "跳出循环 i=" + i);
    }


    public static void main(String[] args) throws InterruptedException {
        VisibilityTest test = new VisibilityTest();

        //线程threadA模拟数据加载场景
        Thread threadA = new Thread(() -> test.load(), "threadA");
        threadA.start();

        //让threadA执行
        Thread.sleep(1000);
        Thread threadB = new Thread(() -> test.refresh(), "threadB");
        threadB.start();
    }




}

运行结果:

分析可见性:

 下面在用一个图来讲解一下JMM概念

 上面还提到了主内存与工作内存的具体的交互操作:

 

下面我来说一个问题,我刚刚提到,只要本地内存共享变量一直存在,那么程序就会从本地缓存里面取数据,那么什么时候本地内存会不存在呢?

第一,栈空间是有限的,到了一定的限度之后,会把变量给清理掉

第二,隔了一段时间本地变量一直没有被使用,也会清理掉

上面两种线程在运行的时候,发现本地没有共享变量了之后,都会去主内存从新加载变量到本地内存。

下面可以看一个代码实例

这个方法是让线程能隔一段纳秒的时间在运行,纳秒的原因是我们可以把时间控制的非常短

 上面就可能会让flag失效,从而去主内存里面获取最新的数据,然后跳出循环

下面讲一个Thread.yield(),他会释放当前线程的时间片,让当前线程进入一个可运行状态,并且保存数据,下次这个线程在次执行的时候,如果发现主内存已经修改了某个共享变量,就会从主内存去获取这个共享变量的值

为什么volatile也可以跳出循环

 我们去看一下jvm中的字节码解释器源码bytecodeInterpreter.cpp

 

然后内部去调用了一个内存屏障处理

 

 x86处理器中利用lock实现类似内存屏障的效果。

lock前缀指令的作用

会等待它之前所有指令完成,并且所有缓冲的写操作写回内存,也就是将store buffer中的内容写入内存之后才开始执行

lock会立即把本地内存修改的变量刷新到主内存里面,同时会让其他处理器中的本地内存的缓存副本失效,它失效,然后又会从主内存读取共享变量

他不是一个内存屏障的指令,但是它有内存屏障的效果

volatile的本质:

下面这个方法

UnsafeFactory.getUnsafe().storeFence();

核心也是

//能够跳出循环    内存屏障
 //System.out.println(count);

//LockSupport.unpark(Thread.currentThread());

上面都是调用内存屏障

还有就是Thread.sleep(1)这种操作也是调用了内存屏障

定义为final也会保证某个变量的可见性

 

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

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

相关文章

变化检测数据集制作详细版

本文记录在进行变化检测数据集制作过程中所使用的代码 首先需要准备相同地区不同时间的两幅影像&#xff0c;裁减成合适大小&#xff0c;如256*256。相同区域命名相同放在两个文件夹下。 接着使用labelme对变化区域进行标注&#xff0c;这里不再进行labelme安装及标注的赘述。…

MAC如何在根目录创建文件

在这之前先明确一下啥是根目录。 打开终端&#xff0c;输入cd /&#xff0c;然后输入 ls 查看根目录下有哪些文件 可以看到 usr、etc、opt 这些文件的地方才叫根目录&#xff0c;而不是以用户命名&#xff0c;可以看到音乐、应用程序、影片、桌面的地方哈 介绍一种叫做软连接…

C++算法:数据流的中位数

题目 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFinder() 初始化 MedianFin…

数据预处理—滑动窗口采样数据

一个简单的例子&#xff1a; # data: 这是要应用滑动窗口采样的输入数据&#xff0c;通常是一个序列&#xff0c;例如列表或NumPy数组。 # window_size: 这是滑动窗口的大小&#xff0c;表示每个窗口中包含的元素数量。 # step_size: 这是滑动窗口移动的步长&#xff0c;表示每…

(※)力扣刷题-栈和队列-用队列实现栈

255.用队列实现栈 使用队列实现栈的下列操作&#xff1a; push(x) – 元素 x 入栈 pop() – 移除栈顶元素 所以后面实现的时候会留下一个元素 top() – 获取栈顶元素 empty() – 返回栈是否为空 注意: 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, …

2023年中国粘度指数改进剂行业需求现状及前景分析[图]

润滑油添加剂指用于提高润滑油使用性能、耐久性及功效&#xff0c;从而增强机械和发动机使用性能的产品&#xff0c;分为单剂和复合剂两大类产品。单剂产品主要是清净剂、分散剂、抗氧抗腐剂、极压抗磨剂、抗氧剂、增粘剂、防锈剂、降凝剂等具有单一特性的添加剂产品&#xff1…

存储数据保护技术——HyperClone克隆与HyperMirror卷镜像技术介绍

目录 HyperClone克隆 基本概念 克隆的特点 工作原理 注意事项 HyperMirror卷镜像 HyperClone克隆 基本概念 克隆是快照技术的一种&#xff1b;在不中断主机业务的情况下&#xff0c;对源数据在某个时间点的一致性数据副本&#xff0c;数据同完成后成为完整的数据副本 …

一天吃透Java集合面试八股文

内容摘自我的学习网站&#xff1a;topjavaer.cn 常见的集合有哪些&#xff1f; Java集合类主要由两个接口Collection和Map派生出来的&#xff0c;Collection有三个子接口&#xff1a;List、Set、Queue。 Java集合框架图如下&#xff1a; List代表了有序可重复集合&#xff0c…

【C++笔记】模板进阶

【C笔记】模板进阶 一、非类型模板参数二、类模板的特化三、模板的分离编译 一、非类型模板参数 我们之前学过的模板虽然能很好地帮我们实现泛型编程&#xff0c;比如我们可以让一个栈存储int类型的数据&#xff0c;一个栈存储double类型的数据&#xff1a; template <cla…

Git 为文件添加执行权限

背景 当你是一台Linux&#xff0c;想要给文件加权限很简单&#xff0c;只需要执行以下命令 chmod x filename就可以给文件添加执行权限&#xff0c;但是如果你是Windows那就很麻烦了 解决方案 假设这里有一个名为 file.sh 的文件&#xff0c;内容如下&#xff1a; #!/bin/…

视频剪辑SDK,实现高效的移动端视频编辑

为了满足企业对视频编辑的需求&#xff0c;美摄提供了iOS/Android端视频编辑SDK技术开发服务&#xff0c;帮助企业快速高效地制作高质量视频。本文将详细介绍美摄的视频编辑SDK的优势和特点&#xff0c;以及如何为企业提供技术解决方案。 随着智能手机的普及和移动互联网的发展…

Java_Jdbc

目录 一.JDBC概述 二.JDBC API 三.ResultSet[结果集] 四.Statement 五.PreparedStatement 六. JDBC API 总结 一.JDBC概述 JDBC 为访问不同的数据库提供了同一的接口&#xff0c;为使用着屏蔽了细节问题Java程序员使用JDBC 可以连接任何提供了 JDBC驱动的数据库系统&am…

(缺省参数)(函数重载)(引用)(内敛)(C++中的nullptr)

&#xff08;缺省参数&#xff09;&&#xff08;函数重载&#xff09;&&#xff08;引用&#xff09;&&#xff08;内敛&#xff09;&&#xff08;atuo用法&#xff09;&&#xff08;C中的NULL&#xff09; 1.缺省参数1.1缺省参数的概念1.2缺省参数的分类1…

谈谈电商App的压测

背景 最近恰逢双十一&#xff0c;大大小小的电商app在双十一之前都会做一次压测&#xff0c;曾经在小公司工作的时候很想知道大公司是如何压测的&#xff0c;有什么高深的压测工具没&#xff0c;本文就来揭露一下 压测真相 在确认使用什么压测工具进行压测之前&#xff0c;我…

(※)力扣刷题-栈和队列-用栈实现队列

使用栈实现队列的下列操作&#xff1a; push(x) – 将一个元素放入队列的尾部。pop() – 从队列首部移除元素。peek() – 返回队列首部的元素。empty() – 返回队列是否为空。 说明: 你只能使用标准的栈操作 – 也就是只有 push to top, peek/pop from top, size, 和 is empt…

R6G alkyne, 6-isomer可在反应中作为底物或催化剂发挥作用

试剂 | 基础知识概述&#xff08;部分&#xff09;: 英文名&#xff1a;R6G alkyne, 6-isomer 分子式&#xff1a;C30H29N3O4 分子量&#xff1a;495.57 Ex &#xff1a;518nm Em&#xff1a;542nm 规格标准&#xff1a;10mg、25mg、50mg&#xff0c;可提供mg级以及kg级的…

系统架构师备考倒计时16天(每日知识点)

1.信息化战略与实施 2.UML图&#xff08;12个&#xff09; 3.结构化设计&#xff08;耦合&#xff09; 4.SMP与AMP的区别&#xff08;多核处理器的工作方式&#xff09; 多核处理器一般有SMP和AMP两种不同的工作方式: SMP(对称多处理技术)&#xff1a;将2颗完全一样的处理器封…

24---WPF缓存

一、什么是缓存&#xff1a; 1.缓存指的是将需要频繁访问的网络内容存放在离用户较近、访问速度更快的系统中&#xff0c;以提高内容访问速度的一种技术。缓存服务器就是存放频繁访问内容的服务器。 2.缓存就是一个临时存放区域--离用户比较近 二、作用--意义---如果系统出现故…

郭明錤修正预测:苹果第二代AirTag推迟2025年量产

10月19日&#xff0c;知名分析师郭明錤调整今年8月关于苹果第2代AirTag的预测&#xff0c;表示该产品量产时间从先前预测的2024年Q4延迟至2025年&#xff0c;并进一步推迟新款上市日期。 AirTag是苹果公司推出的一款蓝牙设备&#xff0c;配合苹果设备的“查找”APP&#xff0c…

【综述】Diffusion Models: A Comprehensive Survey of Methods and Applications

Diffusion Models: A Comprehensive Survey of Methods and Applications 论文&#xff1a;https://arxiv.org/abs/2209.00796 github&#xff1a;https://github.com/YangLing0818/Diffusion-Models-Papers-Survey-Taxonomy 目录 Diffusion Models: A Comprehensive Survey…