Java基础--->并发部分(1)

news2024/9/23 13:29:06

文章目录

  • 线程基本概念
  • 线程的创建方式
  • 线程调度-------常用的方法
  • 线程的生命周期和状态
  • 并发编程的根本原因
  • Java内存模型(JMM)
  • 多线程核心的根本问题
  • volatile关键字
  • 保障原子性
  • synchronized和ReentrantLock的区别

线程基本概念

​ 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序就是一个进程从创建,运行到消亡的过程。

​ 在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

  • 程序:静态的代码

并发:一个CPU同时执行多个任务,指两个或多个事件在同一个时间段内发生。比如:秒杀、多个人做同一件事。

并发编程:例如:买票,抢购,秒杀等场景,有大量的请求访问同一个资源。

​ 会出现线程安全的问题,所以需要通过编程来控制解决让多个线程依次访问资源,称为并发编程。

并行:多个CPU同时执行多个任务,指两个或多个时间在同一时刻发生(同时发生)。比如:多个人同时做不同的事。

进程:运行中的程序,被加载到内存中,是操作系统分配内存的基本单位,每个进程都有自己独立的一块内存空间,一个进程可以有多个线程(至少有一个)

线程:线程是程序处理的基本最小单位,是cpu执行的单元,是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

线程的创建方式

  • 继承Thread类

继承Thread类,重写run() 方法,在run() 方法中写入线程要执行的程序,并实例化自定义的线程子类,也就是创建线程对象,调用start() 方法启动线程

特点:编写简单,可直接操作线程,适用于单继承

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行逻辑
    }
}

// 创建并启动线程
MyThread myThread = new MyThread();
myThread.start();

  • 实现Runnable接口

实现Runnable接口,重写run() 方法,在run() 方法中写入线程要执行的程序,在创建 Thread 对象时将 Runnable 子类实例作为参数传递并调用 start 方法启动线程。

特点:避免单继承局限性,便于共享资源

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行逻辑
    }
}

// 创建并启动线程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();

线程调度-------常用的方法

        Thread thread = new Thread(td,"自定义线程");//创建线程的名称

			thread.start();//启动线程
			thread.join();//这个线程运行完了之后运行其他的    
			thread.getName();//获取线程名称
        	thread.setName();//设置线程的名称
        	thread.setPriority(1);//设置线程优先级(1-10)
       		thread.getPriority();//得到线程优先级       
			thread.sleep(200);//以指定的毫秒数暂停执行
        	thread.currentThread();//返回对当前正在执行的线程对象的引用
        	thread.yield();//让步
        	thread.isAlive();//判断线程是否活着
			setDaemon(bool);//设置线程是否为守护线程。

		object.notify();//唤醒等待的线程
		object.wait(); /*
                       wait()  必须在同步代码块中使用
                       必须是使用同步锁对象调用wait()
                       wait()调用后,锁会释放
                       必须要通过其他线程来唤醒
                    */

线程的生命周期和状态

Java线程在循行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态:

  • new:初始状态,线程被创建出来但是没有调用start()
  • runnable:运行状态,线程被调用了start()等待运行的状态
  • blocked:阻塞状态,需要等待锁释放
  • waiting:等待状态,表示该线程需要等待其它线程做出一些特定动作(通知或中断)
  • time_waiting:等待超时状态,可以在指定的时间后自行返回而不是像waiting那样一直等待
  • terminated:终止状态,表示该线程已经运行完毕
  • 守护线程

并发编程的根本原因

并发编程的根本原因在于计算机处理器的性能增长已经不能再利用频率提升这样的传统方法来提高计算机性能。因此,提高计算机的性能的唯一方法就是增加计算机的处理器核心数和使用并行计算的方式来实现

单核CPU,线程是串行执行,操作系统中有一个叫任务调度器的组件,将CPU的时间片分给不同的线程使用,由于CPU在线程间的切换非常快,人类感觉是同时运行的。

总结为一句话就是:微观串行,宏观并行,一般会将这种线程轮流使用 CPU的做法称为并发,concurrent

多核 CPU下,每个核(core)都可以调度运行线程,这时候线程可以是并 行的。

Java内存模型(JMM)

Java内存模型(Java Memory Model,JMM)是一套规范,定义了多线程的程序在执行时,内存中的各个变量、对象以及执行顺序等行为。

Java内存模型的主要目的是解决在并发编程中,由于线程之间共享同一变量或对象可能导致不正确的计算结果的问题,例如线程安全问题、可见性问题等。

Java内存模型规定了所有变量都存储在主内存中,每个线程都有自己的工作内存,线程在执行时从主内存中读取变量值到工作内存中,执行结束之后将结果写会主内存,不能直接读写主内存中的变量。为了保证不同线程之间的内存可见性,Java内存模型定义了一些同步机制,比如volatile关键字、synchronized关键字、Lock等,这些机制可以防止多线程并发访问同一变量或者对象时出现的竞争问题和数据一致性问题。

总之,Java内存模型是Java并发编程中的重要概念,它规定了共享变量的可见性、易失性和有序性,帮助程序员编写正确、高效的多线程程序。

多线程核心的根本问题

不可见性

​ 多个线程分别同时对共享数据操作,彼此之间不可见,操作完成后写回主内存,有可能出现问题。一个线程对共享变量的修改,不能被另外一个线程立刻看到,如今的多核处理器,每个CPU内核都有自己的缓存,而缓存仅仅可以被自己所在的处理器内核可见,CPU缓存与内存的数据不容易保持一致。

无序性

无序性是指程序的执行顺序不能被预测,即多线程程序中线程的执行顺序不确定,可能出现多个线程交叉执行的情况。这个特性可以提高程序的并发性能,但也为编写并发程序带来了额外的复杂性。
  
在Java中,出现无序性的主要原因是线程之间的工作内存和主内存的数据不一致。Java内存模型规定,每个线程都有自己的工作内存,线程在执行时需要将共享变量从主内存中读取到工作内存中,数据修改后再将数据写回到主内存中。但是,可能存在线程读取了主内存中的旧值,或者其他线程对共享变量的修改对该线程不可见的情况,导致出现无序性问题。
  
为了解决无序性问题,Java提供了volatile关键字,保证变量对所有线程的可见性。使用volatile关键字修饰的共享变量的值在每次被线程访问时,都强制从主内存中读取。这意味着每次访问的值都是最新的,能够避免出现无序性问题。
  
另外,Java 1.5引入了java.util.concurrent包中提供的原子变量类(如AtomicInteger,AtomicLong等)可以使用其提供的CAS操作,完成保证操作的原子性和可见性的操作,它们是线程安全的,可以用来替代那些非原子性的操作。这些类不但保证了原子性,也支持非阻塞算法,避免了使用synchronized关键字导致的线程阻塞和等待。
  
最后还有一个解决无序性问题的方法是使用Java.util.concurrent包下的Lock接口,它提供了比synchronized关键字更细粒度的锁机制。Lock接口提供了多种锁类型,如可重入锁、公平锁、读写锁等,可以根据需要灵活地使用相应的锁类型。
  
综上所述,Java提供了多种方法来解决无序性问题,比如使用volatile,原子变量类,Lock接口等。在编写多线程程序时,需要根据具体情况选择合适的方式,保证程序的正确性和高并发性能。

非原子性:

​ 原子性是指一个操作是不可分割的,要么全部执行成功,要么全部不执行,中间不会出现被其他线程干扰的情况。在多线程编程中,如果涉及到多个线程并发访问同一资源时,并且这些操作需要保证原子性,否则会出现数据不一致的情况。

原子性是拒绝多线程交叉操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作

总结

​ 缓存(工作内存)导致可见性问题,编译(指令重排)优化带来有序性问题,线程切换带来原子性问题。缓存、线程、编译优化的目的和我们写并发程序的目的是相同的,都是提高程序安全性和性能

volatile关键字

当一个线程执行过程中,提取的数据都是从内存中提取的,当其他线程修改后,当前线程获取到的错误的数据。使用volatile关键字就可以解决这些问题,volatile关键字可以用于声明一个变量,它可以确保变量在每次使用时从内存中读取,而在写入时也会立即刷新到内存中。这就保证了可见性和禁止指令重排序优化。

volatile底层实现原理

volatile 修饰的变量在操作前,添加内存屏障,不让它的指令干扰

内存屏障是一种CPU指令,用于限制指令重排序和缓存到内存之间的顺序。内存屏障对程序的执行和优化都有显著影响,可以保证多线程下的程序正确性和一致性。比如,一条内存屏障指令可以禁止编译器和处理器将其后面的指令移到内存屏障指令之前。

​ volatile 修饰的变量添加内存屏障之外,还要通过缓存一致性协议(MESI)将数据写回到主内存,其他工作内存嗅探后,把自己工作内存数据过期,重新从主内存读取最新的数据

保障原子性

1. 通过加锁的方式,让线程互斥执行来保证一次只有一个线程对共享资源访问

2. 在Java中还提供一些原子类(JUC),在低并发情况下使用,是一种无锁实现,

在JUC(java.util.concurrent)中,你可以见到java.util.concurrent.atomic和java.util.concurrent.locks。
  
加锁是一种阻塞式方式实现,原子变量是非阻塞式方式实现

原子类的原子性是通过volatile+CAS实现原子操作的
AtomicInteger类中的value是有volatile关键字修饰的,这就保证了value的内存可见性
  
低并发情况使用AtomicInteger

3. 采用自旋思想
采用CAS机制(Compare-And-Swap)比较并交换

概述:将第一次获取的值存入工作内存作为期望值,对数据进行修改,然后写入主内存,写之前判断与主内存中的值是否一致,一致直接写,不一致更新为新的值。
特点

  • 不加锁,所有的线程都可以对共享数据操作
  • 适合低并发使用
  • 由于不加锁,其他线程不需要阻塞,效率高

缺点:大并发时,不停自旋判断,导致CPU占用率高

ABA问题

某个线程将内存值由A改为了B,再由B改为了A。当另外一个线程使用预期值进行判断时,与内存值相同,当前线程的CAS操作无法判断这个值是否发生过变化。

解决ABA问题可以设置版本号,每次操作改变版本号,可以进行判断

synchronized和ReentrantLock的区别

synchronizedReentrantLock 都可以用来实现 Java 中的线程同步。它们的作用类似,但是在用法和特性上还是有一些区别的。

  1. synchronized 是 Java 内置的关键字,可以修饰代码块和方法,自动获取锁、释放锁,可以避免因为锁的释放问题导致的死锁;而 ReentrantLock 是Java类,只能对某段代码进行修饰,需要手动进行锁的获取和释放。

  2. ReentrantLock 的灵活性更高,比如支持可重入锁、支持公平锁和非公平锁、支持多个条件变量等,而 synchronized 则相对简化,更加方便快捷。

  3. 多个线程争抢 synchronized 的锁时,其中一个线程拿到锁后,其他线程进入锁池等待,直到持有锁的线程释放锁,其他等待线程才能继续竞争锁。而 ReentrantLock 可以灵活地控制锁的公平性和非公平性,以及等待的顺序。

  4. synchronized 在底层是依赖于 JVM 实现的,而 ReentrantLock 是使用 java.util.concurrent 包提供的一种基于接口的可重入锁,这种可重入锁的性能比较优秀,适用于高并发场景。

    综上所述,ReentrantLock 更加灵活,支持更多的特性和操作,适用于复杂的场景;而 synchronized 更加简化,使用方便,适用于一些简单的场景。

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

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

相关文章

【Linux】1.4 基本权限

文章目录 一、shell 命令以及运行原理二、Linux 权限的概念三、Linux 权限管理01.文件类型和访问权限(事物属性)02.文件访问的分类(人)①用户分类②角色划分 03.文件权限值的表现方法04.文件访问权限的相关设置方法(a.…

STC8H8K64U单片机-看门狗配置与讲解

1、看门狗寄存器讲解 (bit7)WDT_FLAG:看门狗溢出标志,看门狗发生溢出时,硬件自动将此位置1,需要软件清零 (bit5)EN_WDT:看门狗使能位 0&#x…

被优化了怎么办?他苦学仨月拿到11koffer

网上有个段子叫做“生活就是起起落落落落落落”。人生在世,本就不易,再加上最近大环境影响,各行各业都在内卷,身为芸芸众生的一员,我们也难免受到影响,面临福利裁剪、降薪、甚至被优化的风险。 大环境我们…

面了20家大厂,发现这样介绍项目经验,显得项目很牛...

我在刚刚开始面试的时候,也遇到了这个问题,也是我第一个思考的问题,如何介绍自己的项目,既可以比较全面的让面试官了解这个项目,同时,也不会让面试官觉得废话太多。经过这么多的面试,我发现&…

JavaWeb+JSP+路径问题+跳转(HTML|Servlet|JSP)|这一篇就够了(超详细)

🙈作者简介:练习时长两年半的Java up主 🙉个人主页:老茶icon 🙊 ps:点赞👍是免费的,却可以让写博客的作者开兴好久好久😎 📚系列专栏:Java全栈,计…

ChatGPT真能取代程序员吗,看看它怎么解释SQL注入漏洞的问题

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注"慕课网"! 作者:Beerus|慕课网讲师 背景 本周在《Web安全渗透测试》课程的QQ群中,有同学提问了一个关于网上一个关于SQL注入…

大淘宝技术斩获NTIRE 2023视频质量评价比赛冠军(内含夺冠方案)

近日,CVPR NTIRE 2023 Quality Assessment of Video Enhancement Challenge比赛结果公布,来自大淘宝音视频技术团队的同学组成「TB-VQA」队伍,从37支队伍中脱颖而出,拿下该比赛(唯一赛道)冠军。此次夺冠是团…

Mysql replace into与on duplicate key update区别

1、replace into REPLACE INTO 首先判断数据是否存在;如果不存在,则插入;如果已存在则更新(先删除再插入 根据主键或唯一索引判断记录是否已存在,所以插入数据的表必须要有主键或者唯一索引!否则的话&…

Java 实现访问Redis哨兵(六)

目录 一、哨兵和复制 1.1 哨兵(sentinal) 1.Redis哨兵主要功能 2.Redis哨兵的高可用 1.2 Redis复制(Replication) 1.数据复制原理(执行步骤) 1.3 Redis 主从复制、哨兵和集群这三个有什么区别 二、Java访问哨兵实现 一、哨兵和复制 谈到Redis服务器的高可用&#xff0c…

【笔记】I2S协议是什么?

什么是I2S协议 I2S协议的介绍I2S协议有什么 这两天在搞ESP32的btAudio库,接触到了I2S,简单做个笔记。 I2S协议的介绍 I2S(Inter—IC Sound)总线, 又称集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标…

2.QT窗口部件

类继承关系图 (上面为Base类,下面为Derived类) 窗口与子部件 窗口:没有父部件的部件,称为顶级部件 子部件:非窗口部件 新建空的qmake项目mywidget1 //mywidget1.proQT core gui greaterThan(QT_MAJO…

这是一个黑科技:C++爬虫~(文末报名C/C++领域新星计划)

目录 写在前面 完整代码 这里必看!! 写在最后 写在前面 现在所有人都知道万能的Python可以做机器学习,可以做人工智能,可以爬取各种小网站,但是你不知道,基于C的正则表达式早就能够爬取各种网络数据啦&a…

华为OD机试——对称美学(通过率只有8.51%???)

用java写的这道题,两个样例都可以通过,但是提交之后最终的通过率只有8.51%???自己搞了半天一直都是这个通过率,然后用网上说的100%通过率的代码也是一样的结果,最后时间到了还是没有拿到满分&am…

这些vue基本语法,你掌握了吗

文章目录 一、 vue 项目重点概念介绍1. 单页面应用程序2. 单文件组件3.数据驱动视图 二、 vue 基本结构1、template2、script3、style 三、 vue 常用指令介绍1、内容渲染指令(1)插值表达式 {{xxx}} —常用方式(2)v-text&#xff0…

session和JWT的应用及区别

文章目录 登录认证(node)一、session1.下载session2.全部配置session3.存储session4.获取session5.销毁session 二、JWT (Json web token)1.JWT 的工作原理2.JWT 的组成3.下载JWT4.生成token5.解密TOken6.配置全局错误中间件 登录认证(node) 一、session 一、在node中使用sess…

还不懂Redis?看完这个故事就明白了!

还不懂Redis?看完这个故事就明白了! 我是Redis 你好,我是Redis,一个叫Antirez的男人把我带到了这个世界上。 说起我的诞生,跟关系数据库MySQL还挺有渊源的。 在我还没来到这个世界上的时候,MySQL过的很辛苦,互联网发展的越来越快,它容纳的数据也越来越多,用户请求也…

全国独家专利药品有哪些品种?全国独家药品产品汇总查阅

独家药品一般是指某一家药企拥有生产和销售某种药品的独家权利,细分有活性成分独家(通用名成分独家)、品种独家(同成分不同剂型)、独家品规,通俗可以分成药品功能独家和产品独家。独家药品经常被人认为是具…

证件拍照扫描——基于C++与深度神经网络实现证件识别扫描并1比1还原证件到A4纸上

前言 数字化时代的到来,越来越多的证件需要进行电子化处理,例如身份证、驾驶证、护照等。在进行电子化处理时,最常见的需求就是将证件照片复制到A4纸上,以便于打印、存档或传输。同时,为了方便信息的录入和管理&#…

一条命令搭建HTTP服务器

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 转载自远程内网穿透的文章:【Python】快速简单搭建HTTP服务器并公网访问「cpolar内网穿透…

word自带公式编辑

快捷键: 公式编辑:alt“” 上标:x^i 空格 下标:x_i 空格 实数R:\doubleR 空格 偏微分算子:“\partial” 极限:“\limit”(按空格后会显示一串很长的式子,再空格就变…