lingo编程的主要方法_java并发编程 --并发问题的根源及主要解决方法

并发问题的根源在哪

首先,我们要知道并发要解决的是什么问题?并发要解决的是单进程情况下硬件资源无法充分利用的问题。而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大。如果将CPU的速度比作火箭的速度,那么内存的速度就像火车,而最惨的磁盘,基本上就相当于人双腿走路。

这样造成的一个问题,就是CPU快速执行完它的任务的时候,很长时间都会在等待磁盘或是内存的读写。

计算机的发展有一部分就是如何重复利用资源,解决硬件资源之间效率的不平衡,而后就有了多进程,多线程的发展。并且演化出了各种为多进程(线程)服务的东西:

  • CPU增加缓存机制,平衡与内存的速度差异
  • 增加了多个概念,CPU时间片,程序计数器,线程切换等,用以更好得服务并发场景
  • 编译器的指令优化,希望在内部充分利用硬件资源

但是这样一来,也会带来新的并发问题,归结起来主要有三个。

  • 由于缓存导致的可见性问题
  • 线程切换带来的原子性问题
  • 编译器优化带来的有序性问题

我们分别介绍这几个:

缓存导致的可见性

CPU为了平衡与内存之间的性能差异,引入了CPU缓存,这样CPU执行指令修改数据的时候就可以批量直接读写CPU缓存的内存,一个阶段后再将数据写回到内存。

但由于现在多核CPU技术的发展,各个线程可能运行在不同CPU核上面,每个CPU核各有各自的CPU缓存。前面说到对变量的修改通常都会先写入CPU缓存,再写回内存。这就会出现这样一种情况,线程1修改了变量A,但此时修改后的变量A只存储在CPU缓存中。这时候线程B去内存中读取变量A,依旧只读取到旧的值,这就是可见性问题。

线程切换带来的原子性

为了更充分得利用CPU,引入了CPU时间片时间片的概念。进程或线程通过争用CPU时间片,让CPU可以更加充分得利用。

比如在进行读写磁盘等耗时高的任务时,就可以将宝贵的CPU资源让出来让其他线程去获取CPU并执行任务。

但这样的切换也会导致问题,那就是会破坏线程某些任务的原子性。比如java中简单的一条语句count += 1。

映射到CPU指令有三条,读取count变量指令,变量加1指令,变量写回指令。虽然在高级语言(java)看来它就是一条指令,但实际上确是三条CPU指令,并且这三条指令的原子性无法保证。也就是说,可能在执行到任意一条指令的时候被打断,CPU被其他线程抢占了。而这个期间变量值可能会被修改,这里就会引发数据不一致的情况了。所以高并发场景下,很多时候都会通过锁实现原子性。而这个问题也是很多并发问题的源头。

编译器优化带来的有序性

因为现在程序员编写的都是高级语言,编译器需要将用户的代码转成CPU可以执行的指令。

同时,由于计算机领域的不断发展,编译器也越来越智能,它会自动对程序员编写的代码进行优化,而优化中就有可能出现实际执行代码顺序和编写的代码顺序不一样的情况。

而这种破坏程序有序性的行为,在有些时候会出现一些非常微妙且难以察觉的并发编程bug。

举个简单的例子,我们常见的单例模式是这样的:

public class Singleton {private Singleton() {}private static Singleton sInstance;public static Singleton getInstance() {if (sInstance == null) {	//第一次验证是否为nullsynchronized (Singleton.class) {   //加锁if (sInstance == null) {	  //第二次验证是否为nullsInstance = new Singleton();  //创建对象}}}return sInstance;}}

即通过两段判断加锁来保证单例的成功生成,但在极小的概率下,可能会出现异常情况。原因就出现在sInstance = new Singleton();这一行代码上。这行代码,我们理解的执行顺序应该是这样:

  1. 为Singleton象分配一个内存空间。
  2. 在分配的内存空间实例化对象。
  3. 把Instance 引用地址指向内存空间。

但在实际编译的过程中,编译器有可能会帮我们进行优化,优化完它的顺序可能变成如下:

  1. 为Singleton对象分配一个内存空间。
  2. 把instance 引用地址指向内存空间。
  3. 在分配的内存空间实例化对象。

按照优化完的顺序,当并发访问的时候,可能会出现这样的情况

  1. A线程进入方法进行第1次instance == null判断。
  2. 此时A线程发现instance 为null 所以对Singleton.class加锁。
  3. 然后A线程进入方法进行第2次instance == null判断。
  4. 然后A线程发现instance 为null,开始进行对象实例化。
  5. 为对象分配一个内存空间。
    6.把Instance 引用地址指向内存空间(而就在这个指令完成后,线程B进入了方法)。
  6. B线程首先进入方法进行第1次instance == null判断。
  7. B线程此时发现instance 不为null ,所以它会直接返回instance (而此时返回的instance 是A线程还没有初始化完成的对象)

最终线程B拿到的instance 是一个没有实例化对象的空内存地址,所以导致instance使用的过程中造成程序错误。解决办法很简单,可以给sInstance对象加上一个关键字,volatile,这样编译器就不会乱优化,有关volatile的具体内容后续再细说。

主要解决办法

通过上面的介绍,其实可以归纳无论是CPU缓存,线程切换还是编译器优化乱序,出现问题的核心都是因为多个线程要并发读写某个变量或并发执行某段代码。那么我们可以控制,一次只让一个线程执行变量读写就可以了,这就是互斥

而在某些时候,互斥还不够,还需要一定的条件。比如一个生产者一个消费者并发,生产者向队列存东西,消费者向队列拿东西。那么生产者写的时候要保证存的时候队列不是满的,消费者要保证拿的时候队列非空。这种线程与线程间需要通信协作的情况,称为同步同步可以说是更复杂的互斥

既然知道了并发编程的根源以及同步和互斥,那我们来看看有哪些解决的思路。其实一共也就三种:

  • 避免共享
  • Immutability(不变性)
  • 管程及其他工具

下面我们分别说说这三种方案的优缺点

避免共享

我们先来说说避免共享,其实避免共享说是线程本地存储技术,在java中指的一般就是Threadlocal。ThreadLocal会为每个线程提供一个本地副本,每个线程都只会修改自己的ThreadLocal变量。这样一来就不会出现共享变量,也就不会出现冲突了。

其实现原理是在ThreadLocal内部维护一个ThreadLocalMap,每次有线程要获取对应变量的时候,先获取当前线程,然后根据不同线程取不同的值,典型的以空间换时间。

所以ThreadLocal还是比较适用于需要共享资源,且资源占用空间不大的情况。比如一些连接的session啊等等。但是这种模式应用场景也较为有限,比如需要同步情况就难以胜任。

Immutability(不变性)

Immutability在函数式中用得比较多,函数式编程的一个主要目的是要写出无副作用的代码,有关什么是无副作用可以参考我以前的文章Scala函数式编程指南(一) 函数式思想介绍。而无副作用的一个主要特点就是变量都是Immutability即不可变的,即创建对象后不会再修改对象,比如scala默认的变量和数据结构都是不可变的。而在java中,不变性变量即通过final修饰的变量,如String,Long,Double等类型都是Immutability的,它们的内部实现都是基于final关键字的。

那这又和并发编程有什么关系呢?其实啊,并发问题很大部分原因就是因为线程切换破坏了原子性,这又导致线程随意对变量的读写破坏了数据的一致性。而不变性就不必担心这个问题,因为变量都是不变,不可写只能读的。在这种编程模式下,你要修改一个变量,那么只能新生成一个。这样做的好处很明显,但坏处也是显而易见,那就是引入了额外的编程复杂度,丧失了代码的可读性和易用性。

因为如此,不变性的并发解决方案其实相对而已没那么广泛,其中比较有代表性的算是Actor并发编程模型,我以前也有讨论过,有兴趣可以看看Actor模型浅析 一致性和隔离性,这种编程模型和常规并发解决方案有很显著的差异。按我的了解,Acctor模式多用在分布式系统的一些协调功能,比如维持集群中多个机器的心跳通信等等。如果在单机并发环境下,还是下面要介绍的管程类工具才是利器。

管程及其他工具

其实最早的操作系统中,解决并发问题用的是信号量,信号量通过两个原子操作wait(S),和signal(S)(俗称P,V操作)来实现访问资源互斥和同步。比如下面这个小例子:

//整型信号量定义
int S;//P操作
wait(S){while(S<=0);S--;
}//V操作
signal(S){S++;
}

虽然信号量方便有效,但信号量要对每个共享资源都实现对应的P和V操作,这使得并发编程中可能要出现大量的P,V操作,并且这部分内容难以抽象出来。

为了更好地实现同步互斥,于是就产生了管程(即Monitor,也有翻译为监视器),值得一提的是,管程也有几种模型,分别是:Hasen模型,Hoare模型和MESA模型。其中MESA模型应用最广泛,java也是参考自MESA模型。这里简单介绍下管程的理论知识,这部分内容参考自进程同步机制-----为进程并发执行保驾护航,希望了解更多管程理论知识的童鞋可以看看。

我们来通过一个经典的生产-消费队列来解释,如下图

79ed77f8114f4814a6e5b6d99f46de9b.png

我们先解释下图中右半部分的内容,右上角有一个等待调用的线程队列,管程中每次只能有一个线程在执行任务,所以多个任务需要等待。然后是各个名词的意思,生产-消费需要往队列写入和取出东西,这里的队列就是共享变量对共享资源进行操作称之为过程(入队和出队两个过程)。而向队列写入和取出是有条件的,写入的时候队列必须是非满的,取出的时候队列必须是非空的,这两个条件被称为条件变量

然后再来看看左半部分的内容,假设线程T1读取共享变量(即队列),此时发现队列为空(条件变量之一),那么T1此时需要等待,去哪里等呢?去条件变量队列不能为空对应的队列中去等待。此时另一个线程T2向共享变量队列写数据,通过了条件变量队列不能满,那么写完后就会通知线程T1。但因为管程的限制,管程中只能有一个线程在执行,所以T1线程不能立即执行,它会回到右上角的线程等待队列等待(不同的管程模型在这里是有分歧的,比如Hasen模型是立即中断T2线程让队列中下一个线程执行)。

解释完这个图,管程的概念也就呼之欲出了,

hansen对管程的定义如下:一个管程定义了一个数据结构和能力为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。

本质上,管程是对共享资源以及对共享资源的操作抽象成变量和方法,要操作共享变量仅能通过管程提供的方法(比如上面的入队和出队)间接访问。所以你会发现管程其实和面向对象的理念是十分相近的,在java中,主要提供了低层次了synchronized关键字和wait(),notify()等方法。同时还提供了高层次的ReenTrantLock和Condition来实现管程模型。

以上~

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

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

相关文章

Mysql中Drop删除用户的名字_mysql5.5 使用drop删除用户

在说这个问题之前我们先讨论下关于在mysql中删除用户的方法和问题&#xff1a;其实在以前我删除mysql中的账号的时候用delete&#xff0c;一直没注意其实用这个命令删除账号会有一个问题就是使用delete删除账号后&#xff0c;只会清除user表的&#xff0c;在其它表中的信息还是…

docker建多个mysql_《容器化系列二》利用Docker容器化技术安装多个mysql

前提说明安装的Linux系统版本为Centos7.x一、安装docker并测试1、安装yum相关工具包///安装yum相关工具包yum install -y yum-utils device-mapper-persistent-data lvm2//发些报错&#xff0c;关闭刚刚睡眠中的进程kill -9 13312//再次执行yum install -y yum-utils device-ma…

mysql 元数据获取_[MySQL] 获取元数据的步骤

[MySQL] 获取元数据的方法 MySQL提供了以下三种方法用于获取数据库对象的元数据&#xff1a; 1)show语句 2)从INFORMATION_SCHEMA数据库里查询相关表 3)命令行程序&#xff0c;如mysqlshow, mysqldump 用SHOW语句获取元数据 MySQL用show语句获取元数据是最常用的方法&#xff0…

在模糊查询中怎样事先加载页面_8种信息类型,中后台产品功能自查清单

产品经理在梳理产品需求文档时需要把每一个功能的逻辑关系、交互方式都整理全面&#xff0c;为了避免疏漏&#xff0c;与开发评审前&#xff0c;建议每位产品都 Check 几遍文档。本文整理了一份中后台产品功能自查清单&#xff0c;供大家参考&#xff0c;如有不全欢迎提建议~中…

mysql 8.0 手动安装教程_mysql 8.0.13手动安装教程

本文为大家分享了mysql 8.0.13手动安装教程&#xff0c;供大家参考&#xff0c;具体内容如下一、步骤解读1.下载MySQL下载地址选择 Downloads-->Community-->MySQL Community Server&#xff0c;然后拉到页面的最低端&#xff0c;点击“下载”。此时一般会提示登陆&#…

gff3转mysql_科学网-把GFF3文件导入MySQL数据库-闫双勇的博文

什么是GFF3?这个一种序列注释文件的格式&#xff0c;基因组注释数据常常会用这种格式来记录序列注释信息&#xff0c;关于这种格式的更多信息&#xff0c;可以在这里学习&#xff1a;http://www.sequenceontology.org/gff3.shtml这里简单说下&#xff0c;怎样把GFF3文件导入My…

mysql时间字段不走索引_MySQL使用=或=范围查询时不走索引

2020-02-27最近一个日志页面查询很慢&#xff0c;然后去跟踪了查询sql&#xff0c;发现日期字段上即使建了索引&#xff0c;查询还是很慢&#xff0c;执行语句还是使用了全表扫描&#xff0c;于是继续分析下去。查询语句类似:select * from logs where createtime > 2020-01…

指数函数中x的取值范围_指数函数x的取值范围是

1、指数函数x的取值范围是a>0且a不1&#xff1b;2、指数函数是重要的基本初等函数之一。一般地&#xff0c;yax函数(a为常数且以a>0&#xff0c;a≠1)叫做指数函数&#xff0c;函数的定义域是 R &#xff1b;3、&#xff0c;在指数函数的定义表达式中&#xff0c;在ax前的…

java excutorthread_JAVA 线程池ThreadPoolExcutor原理探究

概论线程池(英语&#xff1a;thread pool)&#xff1a;一种线程使用模式。线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程…

websocket连接mysql_websocket 使用 spring 的service层 ,进而调用里面的 dao层 来操作数据库 ,包括redis、mysql等通用...

1.前言描述一下今天用websocket踩得坑 ---》空指针异常&#xff01;我想在websocket里面使用service 层的接口&#xff0c;从中获取数据库的一些信息 &#xff0c;使用 Autowired 注解 接口 &#xff0c;报错 空指针异常 &#xff01;&#xff01;&#xff01;查过资料才发…

世上最简单的mysql_最简单易懂的mysql安装教程

今天安装MySQL花了蛮长时间的&#xff0c;感觉坑还是挺多的&#xff0c;写遍文章总结一下。一、安装1.解压zip包到安装目录先从MySQL官网 下载mysql最新的免安装版压缩包解压之后变成这个样子&#xff1a;里面的文件是这个样子下面开始了&#xff0c;请注意2.配置文件在安装目录…

aix系统升级失败提示java_AIX系统补丁升级失败处理

问题描述&#xff1a;现网一台IBM P550小型机&#xff0c;初始版本通过oslevel –r命令检查为5300-02&#xff0c;在IBM官方网站下载5300-06补丁并升级到5300-06后系统报错&#xff0c;缺少sysmgt.websm.apps 5.3.0.60&#xff0c;sysmgt.websm.rte 5.3.0.60两个文件问题处理&a…

java arraylist char,Java基础学习笔记六 Java基础语法之类和ArrayList详解

引用数据类型引用数据类型分类&#xff0c;提到引用数据类型(类)&#xff0c;其实我们对它并不陌生&#xff0c;如使用过的Scanner类、Random类。我们可以把类的类型为两种&#xff1a;第一种&#xff0c;Java为我们提供好的类&#xff0c;如Scanner类&#xff0c;Random类等&a…

matlab 数值解 期权顶级啊,潮盈期权院高胜率交易技巧系列之二----期权交易策略及基于MATLAB统计套利介绍...

主题&#xff1a;高胜率交易技巧系列之二----期权交易策略及基于MATLAB统计套利介绍会场流程&#xff1a;13:30--14:00&#xff1a;参会嘉宾到场签名14:00--14:45&#xff1a;期权知识14:45--15:25&#xff1a;期权交易策略使用15:25--15:35&#xff1a;茶歇15:35--16:35&#…

php的用例图箭头怎么画,需求中如何画用例图 - china008的个人空间 - OSCHINA - 中文开源技术交流社区...

UML用例图用例图主要用来图示化系统的主事件流程&#xff0c;它主要用来描述客户的需求&#xff0c;即用户希望系统具备的完成一定功能的动作&#xff0c;通俗地理解用例就是软件的功能模块&#xff0c;所以是 设计系统分析阶段的起点&#xff0c;设计人员根据客户的需求来创建…

oracle学习数据,Oracle从入门到精通的学习笔记

本次知识点:1.认识SQL的介绍2.掌握scott用户的数据结构3.查询语句之简单查询1.SQL:SQL是指结构化查询语言,在80年代的时候,基本存在80多种数据库,每一种数据库都有自己的的操作命令,也就导致了程序员从一个数据库到另一个数据库的转化时变的极为麻烦,基本就要从新学习.在70年代…

强行更改linux服务器时间,加强Linux服务器安全的20项建议

一般情况下用 Linux 做桌面在默认配置下是很安全的&#xff0c;我在一定程度上同意这个说法(很值得商榷的话题)。不过 Linux 内置的安全模型和工具做得确实很到位&#xff0c;用户只需进行简单的调整和自定义就可以加强 Linux 服务器安全。与恶意用户做斗争对于所有 Linux 系统…

linux终端提示符含义,Linux:终端提示符 (prompt) 不如期生效原因

前言先来简单介绍下, prompt是什么鬼? 顾名思义就是提示符的意思, 看起来和我们遥远, 但实际上只要是每个接触shell的童鞋, 都有看到, 那就是我们在输命令时前面的那串提示符.例如:当然, 这个样式是可以修改的, 这就涉及到我们的PS1和PS2了, 有经验或者以前有设置过的童鞋估计…

skyeye linux qt,ARM仿真器SkyEye的安装及使用

SkyEye是一个开源软件(OpenSource Software)项目&#xff0c;中文名字是"天目"。SkyEye的目标是在通用的Linux和Windows平台上实现一个纯软件集成开发环境&#xff0c;模拟常见的嵌入式系统&#xff0c;可在SkyEye上运行μCLinux以及μC/OS-II等&#xff0c;以及各种…

2g 双核电脑 linux,9208)(奔腾双核E5200/2G/320G)电脑详细技术

处理器型&#xff1a;intel 酷睿2双核 p7350 intel 酷睿2双核 p7450 intel 酷睿2双核 t6600 intel 奔腾双核t4300 intel 奔腾双核 t4400 intel 赛扬双核 t1600 intel 赛扬双核 t3000操作系统&#xff1a;windowsvista home basic dos标配内存&#xff1a;1gb 2gb 硬盘容量&…