100+次提醒:++ 不是线程安全的

疯狂创客圈 Java 分布式聊天室【 亿级流量】实战系列之 -17【 博客园 总入口 】


文章目录

  • 100+次提醒:++ 不是线程安全的
    • 写在前面
    • 一道简单线程安全题,不知道有多少人答不上来
    • 实验:并发的自增运算
    • ++ 运算的原理
    • Java 的原子操作类
    • 写在最后
    • 疯狂创客圈 Java 死磕系列

源码IDEA工程获取链接: Java 聊天室 实战 源码

写在前面

​ 大家好,我是作者尼恩。

​ 前面,已经完成一个高性能的 Java 聊天程序的四件大事:

  1. 完成了协议选型,选择了性能更佳的 Protobuf协议。具体的文章为: Netty+Protobuf 整合一:实战案例,带源码

  2. 介绍了 通讯消息数据包的几条设计准则。具体的文章为: Netty +Protobuf 整合二:protobuf 消息通讯协议设计的几个准则

  3. 解决了一个非常基础的问题,这就是通讯的 **粘包和半包问题。**具体的文章为:Netty 粘包/半包 全解 | 史上最全解读

  4. 前一篇文件,已经完成了 系统三大组成模块的组成介绍。 具体的文章为:Netty聊天程序(实战一):从0开始实战100w级流量应用

在设计客户端之前,发现一个非常重要的基础知识点,没有讲到。这个知识点就是Java并发包。

由于Java并发包将被频繁使用到,所以不得不停下来,先介绍一下。

一道简单线程安全题,不知道有多少人答不上来

尼恩作为技术主管,常常组织组织技术面试,而且往往是第二面。

某次面试,候选人是从重庆一所211大学毕业了一年的初级Java工程师,暂且简称Y君。

在尼恩面试前,Y君已经过了第一关,通过了PM同事的技术面试,PM同事甚至还反馈说Y君的继承不错。理论上,Y君的offer已经没有什么悬念了。

于是,尼恩想前面无数次面试一样,首先开始了多线程方面的问题。

先上来就是砸出一个古老的面试问题:

程序为什么要用多线程,单线程不是很好吗?

多线程有什么意义?

多线程会带来哪些问题,如何解决?

++操作是线程安全的吗?

乖乖,Y君的答案,令人出人意料。

答曰:“我从来没有用过多线,不是太清楚多线程的意义,也不清楚多线程能带来哪些问题”。

乖乖,看一看Y君的简历,这个又是一个埋头干活,被增删改查坑害了的小兄弟!

这已经不是第一个了,我已经记不清楚,有多少面试的兄弟,搞不清楚一这些非常基础的并发编程的知识。

单体WEB应用的时代,已经离我们远去了。 微服务、异步架构的分布式应用时代,已经全面开启。

对于那些面试失败的兄弟,为了提升他们的水平,尼恩都会给他提一个善意的建议。让他们去做一个简单的并发自增运算的实验,看看自增运算是否线程安全的。

实验:并发的自增运算

使用10条线程,对一个共享的变量,每条线程自增100万次。看看最终的结果,是不是1000万?

完成这个小实验,就知道++运算是否是线程安全的了。

实验代码如下:

/*** Created by 尼恩 at 疯狂创客圈*/package com.crazymakercircle.operator;import com.crazymakercircle.util.Print;/*** 不安全的自增 运算*/
public class NotSafePlus
{public static final int MAX_TURN = 1000000;static class NotSafeCounter implements Runnable {public  int amount = 0;public void increase() {amount++;}@Overridepublic void run() {int turn = 0;while (turn < MAX_TURN) {++turn;increase();}}}public static void main(String[] args) throws InterruptedException {NotSafeCounter counter=new NotSafeCounter();for (int i = 0; i < 10; i++) {Thread thread = new Thread(counter);thread.start();}Thread.sleep(2000);Print.tcfo("理论结果:" + MAX_TURN * 10);Print.tcfo("实际结果:" + counter.amount);Print.tcfo("差距是:" + (MAX_TURN * 10 - counter.amount));}
}

运行程序,输出的结果是:

[main|NotSafePlus:main]:理论结果:10000000[main|NotSafePlus:main]:实际结果:9264046[main|NotSafePlus:main]:差距是:735954

也就是说,并发执行后,总计自增1000万次,结果少了70多万次,差距是巨大的,在10%左右。

当然,这只是一次结果,每一次运行,差距都是不同的。大家可以动手运行体验一下。

从结果可以看出,自增运算符不是线程安全的。

++ 运算的原理

自增运算符,至少包括三个JVM指令

  • 从内存取值

  • 寄存器增加1

  • 存值到内存

    这三个指令,在JVM内部,是独立进行的,中间完全可能会出现多个线程并发进行。

比如:当amount=100是,有三个线程读同一时间取值,读到的都是100,增加1后结果为101,三个线程都存值到amount的内存,amount的结果是101,而不是103。

JVM内部,从内存取值,寄存器增加1,存值到内存,这三个操作自身是不可以再分的,这三个操作具备原子性,是线程安全的,也叫原子操作。两个、或者两个以上的原子操作合在一起进行,就不在具备原子性。比如先读后写,那么就有可能在读之后,这个变量被修改过,写入后就出现了数据不一致的情况。

Java 的原子操作类

对于每一种基本类型,在java 的并发包中,提供了一组线程安全的原子操作类。

对于Integer类型,对应的原子操作类是AtomicInteger 类。

java.util.concurrent.atomic.AtomicInteger

使用 AtomicInteger类,实现上面的实验,代码如下:

import java.util.concurrent.atomic.AtomicInteger;/*** 安全的 ++ 运算*/
public class SafePlus
{public static final int MAX_TURN = 1000000;static class NotSafeCounter implements Runnable {public AtomicInteger amount =new AtomicInteger(0);public void increase() {amount.incrementAndGet();}@Overridepublic void run() {int turn = 0;while (turn < MAX_TURN) {++turn;increase();}}}public static void main(String[] args) throws InterruptedException {NotSafeCounter counter=new NotSafeCounter();for (int i = 0; i < 10; i++) {Thread thread = new Thread(counter);thread.start();}Thread.sleep(2000);Print.tcfo("理论结果:" + MAX_TURN * 10);Print.tcfo("实际结果:" + counter.amount);Print.tcfo("差距是:" + (MAX_TURN * 10 - counter.amount.get()));}
}

运行代码,结果如下;

[main|NotSafePlus:main]:理论结果:10000000[main|NotSafePlus:main]:实际结果:10000000[main|NotSafePlus:main]:差距是:0

这一次,10条线程,累加1000w次,结果是1000w。

看起来,如果需要线程安全,需要使用Java并发包中的原子类。

写在最后

​ 下一篇:Netty 中的Future 回调实现与线程池详解。这个也是一个非常重要的基础篇。


疯狂创客圈 Java 死磕系列

  • Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战
  • Netty 源码、原理、JAVA NIO 原理
  • Java 面试题 一网打尽
  • 疯狂创客圈 【 博客园 总入口 】


惊,面N次都不对:++ 操作不是线程全的相关推荐

  1. idea每次都要配置tomcat_电脑每次开机时间都不对?电脑每次开机都要重新设置时间解决方法...

    近期有网友咨询到装机之家晓龙,称自己一台老电脑最近出现一个问题,每次电脑重新开机都需要设置一下时间,关机再开机之后系统时间又归零了,又需要重新设置系统时间.日期,十分麻烦.那么电脑每次开机时间都不对怎 ...

  2. 解决GitLab中使用SSH的git clone总是提示输入密码且任何密码都不对

    解决GitLab中使用SSH的git clone总是提示输入密码且任何密码都不对   笔者最近在新 Linux 中安装 GitLab 后,发现一个诡异的事情.当配置完管理员账号.SSH 密钥之后.开启 ...

  3. 用winformz时间格式不正确_煮八爪鱼,有人用冷水,有人用开水,大厨:都不对,教你正确做法...

    煮八爪鱼,有人用冷水,有人用开水,大厨:都不对,教你正确做法.大家好,我是傻姐美食,生活唯有美食和美景不可辜负.果然是人间最美四月天.四月不但有美丽的风景,还有很多美味的食物,特别是生活在海边的人们, ...

  4. 5 加盐_清洗桑葚时,有人加盐有人加碱,都不对!教你正确做法,太干净了

    大家好,我是炊烟美食,生活中唯有美食和美景不可辜负.夏天真是一个五彩缤纷的季节,有美丽的景色也有丰富的水果,并且水果都是应季的,口感好营养也更丰富.这几天桑葚开始大量成熟,老家地里的桑葚树挂满了一个个 ...

  5. Linux中ctrl+f2进入编辑模式后,localhost怎么输入密码都不对

    Linux中ctrl+f2进入编辑模式后,localhost怎么输入密码都不对 localhost login:****** Password:******* Linux环境下password的输入是 ...

  6. 家用计算机出现时间,电脑每次开机时间都不对?电脑每次开机都要重新设置时间解决方法...

    近期有网友咨询到装机之家晓龙,称自己一台老电脑最近出现一个问题,每次电脑重新开机都需要设置一下时间,关机再开机之后系统时间又归零了,又需要重新设置系统时间.日期,十分麻烦.那么电脑每次开机时间都不对怎 ...

  7. 谁都可以操作的加好友方式

    2016年的时候,我觉得自己很牛逼了. 那时候我很势利. 谁向我请教什么问题,没有等价交换的动作我都懒得搭理他们,甚至我还会说一些看不起他们的话. 而我曾经不客气地对待的一些人现在还是我的朋友,他们还 ...

  8. windows与ubuntu双系统,每次重启windows,时间都不对[closed]

    问题描述 上一个笔记本电脑[thinkpad x250]一直是windows ubuntu双系统状态,用了将近五年了,前段时间突然发现一个问题:每次从ubuntu重启返回windows系统时,时间都不 ...

  9. potplayer 多个进程_操作系统进程与线程基本概念理解

    主要内容 进程 为什么引入进程? 最开始的操作系统是单道批处理的(一个程序处理完,再处理下一个程序)而IO是低速的,就会出现cpu要等待IO的情况:从而降低了实际效率.后来就引入多道批处理:而程序在执 ...

最新文章

  1. Laravel7中Redis队列的使用
  2. 6425C-Lab2 安全高效地管理AD
  3. java 微信退款接口_java版微信和支付宝退款接口
  4. Android Cursor类的概念和用法
  5. [置顶] “河软CSDN2011级表彰暨实习动员大会”顺利召开!
  6. 前端学习(3037):vue+element今日头条管理-把数据放到本地存储
  7. 插入排序算法 ,递归实现_C程序实现递归插入排序
  8. Java排序算法——插入排序
  9. gRPC python封装深度学习算法教程
  10. 编程实现之k均值算法
  11. EJB是什么,以及weblogic和tomcat的区别
  12. 北京市海淀区土地利用总体规划(2006-2020)(zz.is2120.BG57IV3)
  13. Activity透明主题的一个坑
  14. Python学习总结(1)——Python知识清单(基础知识数据科学)
  15. ensp路由器注册_使用ensp进行简单的路由器互连实验
  16. python如何定义正整数_Python如何将一个正整数分解为质因数相乘
  17. ESP32 入门笔记05: BLE 蓝牙客户端和服务器 (ESP32 for Arduino IDE)
  18. javascript当中options的用法
  19. LTE学习笔记:OFDM
  20. 交换机半双工通信测试

热门文章

  1. Beyond Compare 3 中文版的安装注册及添加到右键菜单
  2. python模拟seo快排vps点击代码实操
  3. 如何简单理解贝叶斯决策理论(Bayes Decision Theory)
  4. luogu1830 轰炸III
  5. dockers安装ES
  6. 「数据集」医学图像数据集与竞赛大全
  7. 微信获取用户地理位置信息的原理与步骤
  8. TEC1401.Report开发技术总结 - 第九章 使用BI Publisher开发报表-使用BI Publisher创建RTF模板的语法(4/5)
  9. 6脚自锁开关内部结构
  10. aria2c 下载命令