对于最近并发方面的一些考虑与梳理

题记:总是被绕在一个无法挣脱的并发深渊里,眼睛一闭一挣都是线程的世界。

这边最近是这样的情况:

基于JVM的内存模型,我们知道了多个线程并发访问主存里面的共享数据。这时候在每一个线程中会有一个工作内存的逻辑概念。线程和主存之间的工作方式将会遵循:lock unlock  read load  use assign restore write的执行方式来完成线程和主存之间的数据交换处理操作。

并发的三个概念:原子性   可见性  有序性。

对于原子性:我这边的理解是 不可分割的操作,这个定义有点狭隘。JAVA语言默认对于基本类型变量的读写都是原子操作(不可分割的操作,对应于JAVA内存模型就是read load restore write操作),比如int a=3这种,就是一个原子操作,对于double long型占用64位的变量,虚拟机规范提出可以采用两个32位的方式来读写。这就不具有原子性,但是大多数的编程语言都会将64位的数据的读写操作默认实现为原子性。还有一点需要注意的是对于基本类型变量的自加  自减等操作,本质上是不具有原子性的,例如:a=a+3;JAVA中采用

AtomicInteger这种方式来替换Integer完成自加自减等操作,使其具有原子性。也就没有线程安全问题。

问题: 对于volatile 和 synchronized修饰的变量和同步的代码,和原子性之间的关系,怎么去理解?

volatile会保证在一个线程中对一个共享变量的修改会立即同步至其他线程,也就是对于其他线程会立即可见。使用volatile还是会存在线程的安全问题,例如,两个线程同时将主存中int i=2 load进自己的工作内存中,都准备执行i+1操作,正常逻辑下输出应该是4,

但是 线程1将i=2已经放在栈帧的顶部进行+1之前,线程2已经做完了+1操作,这时候线程1会立即可见线程2的操作,线程1中的i被至于3,但是执行+1的还是原来的2,导致最终主存中的i=3;

使用synchronized修饰的代码段都具有原子性,这边我的理解是在一个线程中执行该代码段的时候,即使CPU切换到其他线程,但是由于共享变量已经被锁住,也无法去执行该代码段,所以对于程序员来说,被synchronized修饰的代码片段是具有原子性,会执行完整而不会在执行过程中被其他线程任意修改变量状态。

对于可见性:

指的是线程1对于共享变量的修改可以对于其他线程可见。怎么去理解呢?JAVA中实现可见性可以使用volatile和synchronized两个操作。使用volatile可以保证一个线程的修改对于其他线程立即可见(前面已经讲过),对于synchronized操作,每个线程获取锁之后,都会首先去主存中load数据,然后操作完成之后,在unlock之前,会将修改完成的数据restore进主存,完成了各个线程之间数据的可见性。

对于有序性

我这边的理解是java中的代码执行在并发的情况下应该按照某种规则顺序的执行,不然会产生结果的不可预测性,原因是指令重排。

对于单CPU单线程来说:

字节码在编译执行的时候会采用指令重排来提高执行效率。为了不影响最后执行结果的正确性,编译和CPU的指令重排必须遵循as-if-serial原则,该原则说的是,无论最后怎么指令重排,都不能改变执行结果的正确性。编译和CPU为了达到该准则,使用依赖原则来保证,

也就说是 在单线程中,两个对同一个变量的读写的操作具有依赖性,对于有依赖性的多个操作,编译和CPU不会进行指令重排,也就保证了结果的正确性。

在多线程中,为了保证最后执行结果的正确性,提出了先行发生原则,如果两个操作不满足先行发生原则,CPU就可能会对其指令重排。

具体的先行发生原则是如下:

1、程序次序规则在一个线程内,书写在前面的代码先行发生于后面的。确切地说应该是,按照程序的控制流顺序,因为存在一些分支结构。

2、Volatile变量规则对一个volatile修饰的变量,对他的写操作先行发生于读操作。

3、线程启动规则。Thread对象的start()方法先行发生于此线程的每一个动作。

4、线程终止规则。线程的所有操作都先行发生于对此线程的终止检测。

5、线程中断规则。对线程interrupt()方法的调用先行发生于被中断线程的代码所检测到的中断事件。

6、对象终止规则。一个对象的初始化完成(构造函数之行结束)先行发生于发的finilize()方法的开始。

7、传递性A先行发生B,B先行发生C,那么,A先行发生C。

8、管程锁定规则。一个unlock操作先行发生于后面对同一个锁的lock操作。

我们可以通过Volatile   和synchronized 对于一些共享的变量或者代码片段进行保护,从而避免了指令重排,保证结果的正确执行。

问题:这里有一个线程start书写的顺序,其实可以认为就是在符合先行发生准则的时候,代码执行的顺序。

线程被CPU随机切换,这是无法预知的,在不符合先行发生准则下,也就是重排序。。。。。

使用Volatile    synchronized 处理之后,会符合先行发生的原则,对于符合先行发生的原则的两个操作来说,严格按照先行发生原则的顺序来顺序执行,也就保证了有序性。

时间: 2024-10-15 21:43:08

对于最近并发方面的一些考虑与梳理的相关文章

[原创]JMeter初次使用总结

引言 最近开发 java 后端项目,对外提供Restful API接口,完整功能开发现已完成. 目前通过单测(68%行覆盖率)已保证业务逻辑正确性,同时也尝试使用JMeter进行压力测试以保证并发性能,现做一些梳理与总结 构建Http接口测试步骤 1.创建一个线程组A 2.在线程组A中创建一个Sampler/Http请求 3.选择新建的http请求,设置服务地址.端口.请求uri.请求参数等 4.线程组配置 如希望测试上述接口并发调用性能,考虑创建100个线程,每个线程调用接口10次,则对线程组

当我们讨论性能测试时,我们在说什么?

说起性能测试,大家会想到哪些词?录制脚本.模拟高并发?性能需求分析.业务流程梳理?监控资源耗用.性能瓶颈定位?优化代码处理逻辑.提升服务器配置? 但这真的是性能测试的本质和最终目的么?这篇博客,聊聊我对软件性能的一些看法和思考... 首先明确一点:技术的存在是为了解决实际的业务发展中遇到的问题和痛点! 随着互联网时代不断深入生活工作的各方面,绝大多数的软件系统都会面临如下三个挑战: 1.日益增长的用户数量: 2.日渐复杂的业务场景: 3.急剧膨胀的数据冲击: 这对于软件系统而言,就意味着及时高效

Mysql的锁机制与PHP文件锁处理高并发简单思路

以购买商品举例: ① 从数据库获取库存的数量. ② 检查一下库存的数量是否充足. ③ 库存的数量减去买家购买的数量(以每个用户购买一个为例). ④ 最后完成购买. 仅仅这几行逻辑代码在并发的情况下会出现问题,自己可以想象一下. 这里暂时就不测试了,下面会针对并发的处理给出测试结果. 创建表: CREATE TABLE `warehouse` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `stock` int(11) NOT NULL

Java并发编程:Concurrent锁机制解析

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

VS C++ 并发编程

1.VS2012及以上版本,支持C++11 thread类的并发编程. 相关材料可以参考博客:http://www.cnblogs.com/rangozhang/p/4468754.html 2.但对其之前的版本,可采用以下方式,实现类成员函数创建子线程实现并发. 首先需实现线程类的run函数,故定义了线程类的头文件和其对应的函数实现,具体如图1,2所示: 图1 线程类的头文件 图2 线程类的实现文件 注意到继承的DerivedThread类,只需将并发执行的函数写在其对应的run()函数内即可

快速入门系列--WCF--06并发限流、可靠会话和队列服务

这部分将介绍一些相对深入的知识点,包括通过并发限流来保证服务的可用性,通过可靠会话机制保证会话信息的可靠性,通过队列服务来解耦客户端和服务端,提高系统的可服务数量并可以起到削峰的作用,最后还会对之前的事务知识做一定补充. 对于WCF服务来说,其寄宿在一个资源有限的环境中,为了实现服务性能最大化,需要提高其吞吐量即服务的并发性.然而在不进行流量控制的情况下,并发量过多,会使整个服务由于资源耗尽而崩溃.因此为相对平衡的并发数和系统可用性,需要设计一个闸门(Throttling)控制并发的数量. 由于

多并发时支付如何保持账户余额的一致性?

转载:http://costlend.com/2016/03/14/dispatch-pay-balance-keep-consistence/ 不管是电商,还是O2O业务都会涉及到支付,而且多速情况下流量比较大,尤其是在做活动的时候.一般支付系统主要有充值,扣费,提现,转账等功能,那么在有些业务场景下,尤其是多并发的情况下,我们在做扣费业务操作时该怎样去保持账户余额的一致呢? Java开发人员可能第一个想法就是在调用扣减的DAO的方法上加上一个synchronized关键字,这个解决办法在单节

shell——wait与多进程并发

在脚本里用&后台打开多个子进程,用wait命令可以使这些子进程并行执行. 例1: fun1(){ while true do echo 1 sleep 1 done } fun2(){ while true do echo 2 sleep 1 done } fun1 & fun2 & wait 例2: #!/bin/bash for ((i=0;i<5;i++)) do sleep 3;echo a done #运行需要15秒. #!/bin/bash for ((i=0;i

架构师养成--7.同步类容器和并发类容器

一.同步类容器 同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作.复合类操作如:迭代(反复访问元素,遍历完容器中的所有元素).跳转(根据指定的顺序找到当前元素的下一个元素).以及条件运算.这些复合操作在多线程并发的修改容器时,可能会表现出意外的行为,最经典的便是ConcurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早期迭代器设计的时候并没有考虑并发修改的问题. 同步类容器:如古老的Vector/HashTable.