i++ 是线程安全的吗?

相信很多中高级的 Java 面试者都遇到过这个问题,很多对这个不是很清楚的肯定是一脸蒙逼。内心肯定还在质疑,i++ 居然还有线程安全问题?只能说自己了解的不够多,自己的水平有限。

那么咱么就从volatile说到i++的线程安全问题

volatile关键字保证了在多线程环境下,被修饰的变量在别修改后会马上同步到主存,这样该线程对这个变量的修改就是对所有其他线程可见的,其他线程能够马上读到这个修改后值.

Thread的本地内存

1、每个Thread都拥有自己的线程存储空间

2、Thread何时同步本地存储空间的数据到主存是不确定的

例子


借用Google JEREMY MANSON 的解释,上图表示两个线程并发执行,而且代码顺序上为Thread1->Thread2

「1、不用 volatile」

假如ready字段不使用volatile,那么Thread 1对ready做出的修改对于Thread2来说未必是可见的,是否可见是不确定的.假如此时thread1 ready泄露了(leak through)了,那么Thread 2可以看见ready为true,但是有可能answer的改变并没有泄露,则thread2有可能会输出 0 (answer=42对thread2并不可见)

「2、使用 volatile」

使用volatile以后,做了如下事情

1、每次修改volatile变量都会同步到主存中
2、每次读取volatile变量的值都强制从主存读取最新的值(强制JVM不可优化volatile变量,如JVM优化后变量读取会使用cpu缓存而不从主存中读取)
3、线程 A 中写入 volatile 变量之前可见的变量, 在线程 B 中读取该 volatile 变量以后, 线程 B 对其他在 A 中的可见变量也可见. 换句话说, 写 volatile 类似于退出同步块, 而读取 volatile 类似于进入同步块

所以如果使用了volatile,那么Thread2读取到的值为read=>true,answer=>42,当然使用volatile的同时也会增加性能开销

注意

volatile并不能保证非源自性操作的多线程安全问题得到解决,volatile解决的是多线程间共享变量的「可见性」问题,而例如多线程的i++,++i,依然还是会存在多线程问题,它是无法解决了.如下:使用一个线程i++,另一个i--,最终得到的结果不为0

public class VolatileTest {

    private static volatile int count = 0;    private static final int times = Integer.MAX_VALUE;

    public static void main(String[] args) {

        long curTime = System.nanoTime();

        Thread decThread = new DecThread();        decThread.start();

        // 使用run()来运行结果为0,原因是单线程执行不会有线程安全问题        // new DecThread().run();

        System.out.println("Start thread: " + Thread.currentThread() + " i++");

        for (int i = 0; i < times; i++) {            count++;        }

        System.out.println("End thread: " + Thread.currentThread() + " i--");

        // 等待decThread结束        while (decThread.isAlive());

        long duration = System.nanoTime() - curTime;        System.out.println("Result: " + count);        System.out.format("Duration: %.2fs\n", duration / 1.0e9);    }

    private static class DecThread extends Thread {

        @Overridepublic void run() {            System.out.println("Start thread: " + Thread.currentThread() + " i--");            for (int i = 0; i < times; i++) {                count--;            }            System.out.println("End thread: " + Thread.currentThread() + " i--");        }    }}

最后输出的结果是

Start thread: Thread[main,5,main] i++ Start thread: Thread[Thread-0,5,main] i-- End thread: Thread[main,5,main] i-- End thread: Thread[Thread-0,5,main] i-- Result: -460370604 Duration: 67.37s

原因是i++和++i并非原子操作,我们若查看字节码,会发现

void f1() { i++; }

的字节码如下

void f1();Code:0: aload_01: dup2: getfield #2; //Field i:I5: iconst_16: iadd7: putfield #2; //Field i:I10: return

可见i++执行了多部操作, 从变量i中读取读取i的值 -> 值+1 -> 将+1后的值写回i中,这样在多线程的时候执行情况就类似如下了

Thread1             Thread2r1 = i; r3 = i; r2 = r1 + 1; r4 = r3 + 1;i = r2; i = r4;

这样会造成的问题就是 r1, r3读到的值都是 0, 最后两个线程都将 1 写入 i, 最后 i 等于 1, 但是却进行了两次自增操作

可知加了volatile和没加volatile都无法解决非原子操作的线程同步问题

线程同步问题的解决

Java提供了java.util.concurrent.atomic 包来提供线程安全的基本类型包装类,例子如下

package com.qunar.atomicinteger;

import java.util.concurrent.atomic.AtomicInteger;

/** * @author zhenwei.liu created on 2013 13-9-2 下午10:18 * @version $Id$ */public class SafeTest {

    private static AtomicInteger count = new AtomicInteger(0);    private static final int times = Integer.MAX_VALUE;

    public static void main(String[] args) {

        long curTime = System.nanoTime();

        Thread decThread = new DecThread();        decThread.start();

        // 使用run()来运行结果为0,原因是单线程执行不会有线程安全问题        // new DecThread().run();

        System.out.println("Start thread: " + Thread.currentThread() + " i++");

        for (int i = 0; i < times; i++) {            count.incrementAndGet();        }

        // 等待decThread结束        while (decThread.isAlive());

        long duration = System.nanoTime() - curTime;        System.out.println("Result: " + count);        System.out.format("Duration: %.2f\n", duration / 1.0e9);    }

    private static class DecThread extends Thread {

        @Override        public void run() {            System.out.println("Start thread: " + Thread.currentThread() + " i--");            for (int i = 0; i < times; i++) {                count.decrementAndGet();            }            System.out.println("End thread: " + Thread.currentThread() + " i--");        }    }}

输出

Start thread: Thread[main,5,main] i++ Start thread: Thread[Thread-0,5,main] i-- End thread: Thread[Thread-0,5,main] i-- Result: 0 Duration: 105.15

作者:zemliu来源:cnblogs.com/zemliu/p/3298685.html

最后给大家送下福利,大家可以关注Java核心技术公众号,在后台回复 “福利”可以获取一份我整理的最新Java面试题资料。

最近好文分享

在 Java 中如何优雅地判空,写得太好了!

这么好用的Java工具类库,你居然不知道?

求求你不要在用 !=null 判空了!

注意了!千万不要重写service方法!

BigDecimal一定不会丢失精度吗?

建议使用LocalDateTime,而不是Date?

更多请扫码关注 • Java核心技术

一个分享Java核心技术干货的公众号欢迎大家在看、转发

qthread run结束了算销毁吗_拼多多,一面,i++ 是线程安全的吗?一脸蒙逼!相关推荐

  1. qthread run结束了算销毁吗_对 精致码农大佬 说的 Task.Run 会存在 内存泄漏 的思考...

    一:背景 1. 讲故事 这段时间项目延期,加班比较厉害,博客就稍微停了停,不过还是得持续的技术输出呀! 园子里最近挺热闹的,精致码农大佬分享了三篇文章: 为什么要小心使用 Task.Run [http ...

  2. qthread run结束了算销毁吗_会计职称考试已结束,证书怎么领?

    2020年会计职称考试已结束,坐等领证书! 那么,问题来了?证书什么时候可以领?需要准备什么资料?证书长啥样?通过考试了还需要继续教育吗? 会计职称证书什么时候领? 01 2020年初级.中级会计查分 ...

  3. qthread run结束了算销毁吗_Java线程的run()方法和start()方法有什么区别?

    由于Java是支持单继承的(接口除外),所以我们普遍启动线程的方式都是实现Runnable接口并重写run()方法.先来看下面一个简单的实例: public class MyRunnable impl ...

  4. 弘辽科技:拼多多批发单算销量吗?拼多多刚开店怎么有销量

    关于提起大家在拼多多开店,肯定和淘宝开店有很多是不一样的,例如两个平台算销量的方式就是不一样,不少小伙伴们在拼多多还卖出去了批发单,批发单数量很多,那么.拼多多批发单算销量吗?拼多多刚开店怎么有销量? ...

  5. 3331付款方式怎么写_拼多多怎么刷单 为什么要刷单

    如今无论是哪个网络购物平台都有很多的商家朋友为了追逐利益开始在店铺当中刷单,就连以拼团低价为主的拼多多也不例外.下面小编就来为大家介绍一下拼多多刷单的流程以及原因,希望会给大家带来帮助. 拼多多刷单流 ...

  6. airpods2怎么查正品 ios11系统_拼多多AirPods2开箱评测,4种办法教你验真假,10个AirPods技巧教你玩...

    大家好,Apple今天给大家分享一下拼多多上车AirPods 2无线充电盒版的经验,顺便整理了一波AirPods使用技巧,希望你用得上. 入手理由 自从去年10月份入手了iPhone XR,其实就挺想 ...

  7. gettype拿不到值_拼多多场景实操——这样实操场景推广有效拿高投产【下篇】...

    大家好我是沐风,之前我们说过场景出价的问题,但我们现在关注比较多的就是如何提高新品ROI.因为新品很多情况下ROI是达不到平衡值是亏损的,所以接下来我要说一下ROI的优化之路. 一.新建推广计划 写一 ...

  8. python 拼多多_拼多多现重大BUG被“薅羊毛”,教你如何用Python简单褥羊毛

    import time from urllib.parse import parse_qs import requests from bs4 import BeautifulSoup from sel ...

  9. java常用的网关有哪几种_拼多多java开发一面、二面合并面经

    作者:reed,一个热爱技术的斜杠青年,程序员面试联合创始人 一.项目方面 首先上来简单做一下自我介绍.然后让介绍简历里的项目.说下项目里的难点,技术架构.平时开发过程中都遇到过哪些难题? 平时都这么 ...

最新文章

  1. 报名 | IBM苏中:从深蓝到AlphaGo,从大数据到认知商业
  2. MAC EI Capitan上更新系统自带SVN版本号(关闭SIP方能sudo rm)
  3. linux端口被攻击,Linux 常见攻击端口封杀表
  4. Hive中生成随机唯一标识ID的方法
  5. IDEA:生成javadoc/断点调试/缓存和索引的清理
  6. OJ1040:(递推思想高阶)数列求和1
  7. 如何关闭小娜进程_Python多进程之进程间通信 - Pipe amp; Queue
  8. 【个人网站搭建教程】阿里云服务器+宝塔+wordpress
  9. PHP问题 —— The use statement with non-compound name
  10. java String字符串拼接原理
  11. 如何利用SQL注入进行爆库
  12. 家乡旅游风景区介绍——茂名风景区网页设计HTML+CSS+JavaScript
  13. Windows搭建SFTP文件服务器
  14. iis7无法写入配置文件,更换进入方式解决
  15. 计算机输入出设备课件,《电脑输入设备》PPT课件.ppt
  16. linux 修改主机名 命令,Linux修改主机名命令详解
  17. 【金猿投融展】DataPipeline——成为中国的世界级数据中间件厂商
  18. 报错 jinja2.exceptions.TemplateSyntaxError: Unexpected end of template. Jinja was looking for the foll
  19. java adminlte 使用_AdminLTE的使用(转)
  20. java获取上级目录_Java如何获取文件的父目录或上级目录?

热门文章

  1. 【网络】解决‘ipconfig不是内部或外部命令,也不是可运行的程序
  2. Kubernetes的三种集群外部访问方式及使用场景说明:NodePort、LoadBalancer和Ingress
  3. 【视频】v-bind的使用
  4. 【视频】vue组件的局部注册
  5. 操作系统里的内存碎片的解决办法
  6. Dubbo Admin服务测试功能
  7. python数据处理不用编程_用Python玩转数据数据处理相关小例编程题
  8. 第一行代码学习笔记第四章——探究碎片
  9. python 财务报表审计_python 自动化审计
  10. MySQL索引(B+Tree 索引、哈希索引、全文索引、 空间数据索引)、索引优化、优点、使用场景