CAS算法有三个操作数,通过内存中的值(V)、预期原始值(A)、修改后的新值 。
如果内存中的值和预期原始值相等,就将修改后的新值保存到内存中 。如果内存中的值和预期原始值不相等 , 说明共享数据已经被修改,放弃已经所做的操作 , 然后重新执行刚才的操作 , 直到重试成功 。Java中Unsafe 中的getAndAddInt就是使用的这个算法,不妨详细解读下其代码 。

文章插图
到这里还涉及到一个线程变量修改同步问题,由于计算机结构复杂性,CPU、Mem等各级缓存特性、不同操作系统、不同厂商硬件等等,其中有着很多缓存/同步设计;为了屏蔽这些复杂性,java提供了volatile 关键字来进行保证 。截取一段The Java Language Specification (Java SE 10 Edition)原文:

文章插图
抓重点的理解:字段被声明为volatile,在这种情况下,Java内存模型确保所有线程都看到变量的一致值 。
试一试,多线程性能更好?按照前面解决的思路,修改下之前的代码进行测试下 。另外将耗时也记录一下:

文章插图
是不是发现,val 的数值已经和单线程的一致了都是 1000,没有并发问题了 。性能上从这个例子可以看到,单线程耗时6ms,多线程耗时29ms 。不用质疑结果是没错的,明显多线程耗时更高 。
可以看出多线程运行简单程序并不一定能够提升性能,因为其开启线程有相关的开销;同时看到其 复杂性高、维护成本高、可读性降低 等缺陷 。对于简单业务逻辑场景,不建议用多线程 。
在此基础上,加上模拟下相关业务逻辑,模拟逻辑执行doSomeThings() , 模拟实现逻辑就是线程休眠 1ms 。相关代码,耗时记录如下:

文章插图
这个例子里面 多线程性能优势,与单线程的1914ms 相比多线程只需要 262ms 。当然具体提升的数值和运行的机器、CPU等等有关系 , 笔者电脑是 4核8线程的情况 。
本篇总结下 , 介绍了进程、线程以及相关发展史;展示了一个具体的并发问题;详细分析了并发问题的发生原因以及解决办法 。最后对多线程并发程序进行了验证,以及相关性能上的探究 。
【Java并发编程 | 从进程、线程到并发问题实例解决】写在最后 , 文章中使用的Unsafe 类的功能,在实际编程中绝大部分情况下都不会使用 ;更多地使用 java.util.concurrent 下提供的功能 。比如例子中的多线程操作整数加1,应该使用的是 AtomicInteger。关于Java并发编程其他技巧后续文章中,接着进行讲解 。
欢迎长期关注公众号/头条号(Java研究者)
推荐阅读
- 浅谈 Golang 插件机制
- 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例
- 学习ASP.NET Core Blazor编程系列五——列表页面
- centos7中配置java + mysql +jdk+使用jar部署项目
- Java实现6种常见排序
- 数据结构与算法【Java】08---树结构的实际应用
- 学习ASP.NET Core Blazor编程系列四——迁移
- 二 Java之POI导出Excel:多个sheet
- 15 JavaObject类
- 【设计模式】Java设计模式 - 命令模式