惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

技术改变视野

为程序员、设计师打造的摸鱼社区:摸鱼派! - 技术改变视野 大白话 | Java初学指♂男:“说说初学的误区与死结”( 壹 | 反射与Field ) - 技术改变视野 大白话之耦合性:什么是耦合性和内聚性?用编程语言实例讲解! - 技术改变视野 🎨 适用于 Solo / Bolo 的清新高级主题 —— Sakura | 移植自 WordPress 主题 - 技术改变视野 我在 GitHub 上的开源项目 - 技术改变视野 为IDEA/Git配置GPG密钥,点亮GitHub Verified Commit标识 - 技术改变视野 从 SpringMVC 转为使用 SpringBoot —— 手把手带你使用 Intellij IDEA 最快速地搭建 SpringBoot+MyBatis(无 XML)+Thymeleaf - 技术改变视野 无题 - 技术改变视野 年轻人的第一辆京牌电动车 - 技术改变视野 我们忙碌且漫长的一生,有多久是为自己而活? - 技术改变视野 一个最简单的实例理解Semaphore在Java中的作用 - 技术改变视野 说说《后浪》 - 技术改变视野 离线使用 Google Fonts:让你的网站更精致 | goofoffline 神器,一键下载生成离线字体库! - 技术改变视野 实例带你学会简单的Java Thread多线程 - 技术改变视野 一个实例理解Java的接口(interface)用处与用法 - 技术改变视野 一个实例带你理解JavaBean - 技术改变视野 WebFilter-SpringBoot过滤器注解实例讲解 - 技术改变视野 实例带你搞懂Java多线程&&线程池之(壹):线程池与多线程的关系和区别 - 技术改变视野 实例带你搞懂Java多线程&&线程池之(贰):简单的线程池应用 - 技术改变视野 实例带你获取多线程Thread的返回值之 (壹) - Callable的运行 - 技术改变视野 实例带你获取多线程Thread的返回值之 (贰) - Callable配合线程池返回数据 - 技术改变视野 一个实例理解Java Runnable多线程用处与用法 - 技术改变视野 从零开始OpenSSL之 (壹) - 使用genrsa、rsa、rsautl生成公私钥 - 技术改变视野 从零开始 OpenSSL 之 (贰) - 使用 rsautl 解密文件 - 技术改变视野 大白话之Docker(壹):快速入门&&简单官方实例 - 技术改变视野 大白话之Docker(贰):简单部署一个Tomcat服务并发布内容 - 技术改变视野 大白话之从零讲解DVWA(壹)-SQL注入(SQL Injection) Low Level - 技术改变视野 大白话之从零讲解DVWA(贰)-SQL注入(SQL Injection) Medium/High Level - 技术改变视野 Java日志插件-Slog4J下载 - 技术改变视野 Log-MySQL root用户登录后无法查看数据库全部表/正常访问数据库 Access denied for user 'root'@'localhost' to database - 技术改变视野 大白话之Docker(叁):制作一个运行Tomcat服务端的Docker镜像 - 技术改变视野 Github仅保留指定文件/文件夹当前Commit,删除所有历史记录,清除「敏感信息」 - 技术改变视野 将Tomcat、MySQL从Linux迁移到Windows的心路历程(干货):令人恐惧的字符编码 - 技术改变视野 Spring: java.lang.NoSuchMethodError: clearCache | ClassNotFoundException | Error during artifact deployment 思路及解决办法 - 技术改变视野 大白话之Java反射-初学最迷的概念:能干啥?咋用? - 技术改变视野 大白话之Java面向接口编程:最“正经”的中文实例讲解,看不懂来打我! - 技术改变视野 大白话 | 课堂实践:使用Iterator对数组进行遍历 - 技术改变视野 大白话之fail-fast | fail-safe:为什么会有这个机制?它有什么作用? - 技术改变视野 刨根问底 | 大白话:在使用注解后,框架是怎么知道你哪个方法使用了注解的?用@RequestMapping注解举例详解! - 技术改变视野 大白话之必会Java Atomic | 线程一点也不安全(二):Atomic的ABA问题会导致什么情况?如何解决? - 技术改变视野 随笔 | 奇淫技巧 | Java:记return和短路运算符的妙用 - 技术改变视野 随笔 | Tomcat:续-从Linux迁移到Windows编码问题彻底解决 - 技术改变视野 大白话之AutoClosable接口 | try-with-resources:JDK1.7的新特性,提高重复回收效率 - 技术改变视野 大白话之Java多线程join方法:开局一张图,试学一分钟,你就费和我一样,理解介个方法 - 技术改变视野 大白话之Java Stream流:将类数组流化,便捷批量修改,通俗讲解! - 技术改变视野 Java 困扰三周の问题:使用byte[]或skip()方法读取字节流Stream文件尾部多/少/缺字节解决方法 - 技术改变视野 大白话之Java级联调用:一个类,一条语句,同时调用好几个方法,串成一串 - 技术改变视野
大白话之必会Java Atomic | 线程一点也不安全(一):比自增和synchronized更快速、靠谱的原子操作(调用C语言) - 技术改变视野
adlered · 2019-12-09 · via 技术改变视野

前言

阅读本篇文章,你需要对下方知识有所了解:

  • synchronized关键词的作用
  • 线程池的作用(这里

不靠谱和慢动作

在多线程环境下:

操作 靠谱程度 执行速度
i++ 自增运算 没戏 不赖
synchronized 贼棒 太废

不靠谱的自增

操作类

假如我们现在有一个变量:num
我们这个变量设置两个方法:

方法 返回值 作用
plus() void 将num自增(+1)
getNum() Integer 返回num的值

代码如下:

 1class Num {
 2    Integer num = 0;
 3
 4    public void plus() {
 5        num++;
 6    }
 7
 8    public Integer getNum() {
 9        return num;
10    }
11}

主类

然后在另一个类主方法中新建一个缓存线程池

1ExecutorService executorService = Executors.newCachedThreadPool();

当我们执行executorService.execute(new Runnable() {})时,缓存线程池会将指定的对象以非阻塞的方式提交到队列中。

随后再写一个循环,调用100次plus()方法,此时num值应为100

 1        Num num = new Num();
 2        ExecutorService executorService = Executors.newCachedThreadPool();
 3        for (int i = 0; i < 100; i++) {
 4            /*
 5            下方语句可套用Lambda表达式,替换为:
 6            executorService.execute(() -> num.plus());
 7             */
 8            executorService.execute(new Runnable() {
 9                @Override
10                public void run() {
11                    num.plus();
12                }
13            });
14        }

好。那么复制下方的完整代码,并运行得到结果:

 1import java.util.concurrent.ExecutorService;
 2import java.util.concurrent.Executors;
 3
 4public class Main {
 5    public static void main(String[] args) {
 6        Num num = new Num();
 7        ExecutorService executorService = Executors.newCachedThreadPool();
 8        for (int i = 0; i < 100; i++) {
 9            /*
10            下方语句可套用Lambda表达式,替换为:
11            executorService.execute(() -> num.plus());
12             */
13            executorService.execute(new Runnable() {
14                @Override
15                public void run() {
16                    num.plus();
17                }
18            });
19        }
20        //当线程池内线程全部执行完毕后,关闭线程池
21        executorService.shutdown();
22        //返回num的值
23        System.out.println(num.getNum());
24    }
25}
26
27class Num {
28    Integer num = 0;
29
30    public void plus() {
31        num++;
32    }
33
34    public Integer getNum() {
35        return num;
36    }
37}

你会发现运行结果本应该是100的,但是结果却只能看命:大概率在90-100之间徘徊的结果。

为什么呢?

自增运算

让我们看看自增运算会进行哪些操作:

3.png

也就是说,如果有两个线程正巧同时读取了变量num,那么运算后返回的结果很有可能出错,除非只使用单线程进行操作。

所以说,线程一点也不安全。

有点慢的同步

synchronized为方法添加了一个锁,如果有线程在占用,其它线程就会被阻塞,所以可以保证最终数值的正确。

1    public synchronized void plus() {
2        num++;
3    }

我将循环改为了10000次,并做了如下统计:

组别 是否使用synchronized 执行结果 花费时间
0 9316 61912170ns
0 10000 42229795ns
--- --- --- ---
1 9142 42752371ns
1 10000 78747787ns
--- --- --- ---
2 9495 54361835ns
2 10000 47179626ns
--- --- --- ---
3 9326 44193545ns
3 10000 128409937ns

我们可以比较清晰的看到,使用了synchronized关键字的方法执行速度要慢上一拍,这是因为synchronized的线程同步操作相当于强行将多线程“捋”成了单线程。

Atomic

Compare And Swap

Compare And Swap(CAS),即“比较并交换”。

CAS中有三个值:

V 将要修改的变量
E 在预期中,该变量修改前的值
N 如果符合预期,将变量修改的值

我们还是同样用一张思维导图,说明CAS的逻辑:

4.png

CAS是个倔强且严谨的流程,如果num的值与它运行时所记录的值不同的话,它会尝试重新获取num的值,并再次重复操作。

应用

Atomic便是遵循了CAS原则的原子类,它能可靠地对数据进行修改

5.png

上图是Atomic中提供的一些数据类型的实现类。让我们修改一下自己的实例。

 1class Num {
 2    private AtomicInteger num = new AtomicInteger(0);
 3
 4    public void plus() {
 5        num.incrementAndGet();
 6    }
 7
 8    public Integer getNum() {
 9        return num.get();
10    }
11}
12

套用上方的统计表,我们对Atomic的性能进行多次测试:

组别 使用的方法 执行结果 花费时间
0 自增 9316 61912170ns
0 synchronized 10000 42229795ns
0 Atomic 10000 44210059ns
--- --- --- ---
1 自增 9142 42752371ns
1 synchronized 10000 78747787ns
1 Atomic 10000 53520536ns
--- --- --- ---
2 自增 9495 54361835ns
2 synchronized 10000 47179626ns
2 Atomic 10000 89278829ns
--- --- --- ---
3 自增 9326 44193545ns
3 synchronized 10000 128409937ns
3 Atomic 10000 53277442ns

Atomic相比较synchronized关键字执行时间要稍快一些。

后语

至此,就是本章全部的内容了。

请思考:

  • 截至本章的学习内容,Atomic有哪些缺点?
  • 在链表中,CAS模型E所存储的是什么?
  • 为什么Atomic原子性的?
  • AtomicCAS模型中,会不会出现E始终不正确,陷入死循环的情况?

至此,其实Atomic还是有出错的几率的。下一章我们将讲述Atomic可能导致的ABA问题Atomic的底层实现Unsafe类以及Atomic的缺点。

前往下一章:Atomic的ABA问题会导致什么情况?如何解决?