Java自增原子性问题(测试Volatile、AtomicInteger)

  这是美团一面面试官的一个问题,后来发现这是一道面试常见题,怪自己没有准备充分:i++;在多线程环境下是否存在问题?当时回答存在,接着问,那怎么解决?。。。好吧,我说加锁或者synchronized同步方法。接着问,那有没有更好的方法?

  经过一番百度、谷歌,还可以用AtomicInteger这个类,这个类提供了自增、自减等方法(如i++或++i都可以实现),这些方法都是线程安全的。

一、补充概念



1.什么是线程安全性?

  《Java Concurrency in Practice》中有提到:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

2.Java中的“同步”

  Java中的主要同步机制是关键字“synchronized”,它提供了一种独占的加锁方式,但“同步”这个术语还包括volatile类型的变量,显式锁(Explicit Lock)以及原子变量。

2.原子性

  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型)这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++;这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

二、实例源码


 1 public class IncrementTestDemo {
 2
 3     public static int count = 0;
 4     public static Counter counter = new Counter();
 5     public static AtomicInteger atomicInteger = new AtomicInteger(0);
 6     volatile public static int countVolatile = 0;
 7
 8     public static void main(String[] args) {
 9         for (int i = 0; i < 10; i++) {
10             new Thread() {
11                 public void run() {
12                     for (int j = 0; j < 1000; j++) {
13                         count++;
14                         counter.increment();
15                         atomicInteger.getAndIncrement();
16                         countVolatile++;
17                     }
18                 }
19             }.start();
20         }
21         try {
22             Thread.sleep(3000);
23         } catch (InterruptedException e) {
24             e.printStackTrace();
25         }
26
27         System.out.println("static count: " + count);
28         System.out.println("Counter: " + counter.getValue());
29         System.out.println("AtomicInteger: " + atomicInteger.intValue());
30         System.out.println("countVolatile: " + countVolatile);
31     }
32
33 }
34
35 class Counter {
36     private int value;
37
38     public synchronized int getValue() {
39         return value;
40     }
41
42     public synchronized int increment() {
43         return ++value;
44     }
45
46     public synchronized int decrement() {
47         return --value;
48     }
49 }

  输出结果:

static count: 9952
Counter: 10000
AtomicInteger: 10000
countVolatile: 9979

  第一行与最后一行,每次运行将得到不同的结果,但是中间两行的结果相同。

  通过上面的例子说明,要解决自增操作在多线程环境下线程不安全的问题,可以选择使用Java提供的原子类,或者使用synchronized同步方法。

  而通过Volatile关键字,并不能解决非原子操作的线程安全性。Volatile详解

三、Java中的自增原理



  虽然递增操作++i是一种紧凑的语法,使其看上去只是一个操作,但这个操作并非原子的,因而它并不会作为一个不可分割的操作来执行。实际上,它包含了三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。这是一个“读取 - 修改 - 写入”的操作序列,并且其结果状态依赖于之前的状态。

  下面写一个简单的类,用jdk中的工具javap来反编译Java字节码文件。

/**
 * @author zhengbinMac
 */
public class TestDemo {
    public static int count;

    public void code() {
        count++;
    }
}
localhost:Increment zhengbinMac$ javap -c TestDemo
警告: 二进制文件TestDemo包含Increment.TestDemo
Compiled from "TestDemo.java"
public class Increment.TestDemo {
  public static int count;

  public Increment.TestDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public void code();
    Code:
       0: getstatic     #2                  // Field count:I
       3: iconst_1
       4: iadd
       5: putstatic     #2                  // Field count:I
       8: return
}

  如上字节码,我们发现自增操作包括取数(getstatic  #2)、加一(iconst_1和iadd)、保存(putstatic  #2),并不是我们认为的一条机器指令搞定的。

参考博文:

关于Java自增操作的原子性分析

JAVA中如何保证线程安全以及主键自增有序

Java - 并发 atomic, synchronization and volatile

java并发之原子性与可见性(一)

时间: 2024-07-28 22:44:16

Java自增原子性问题(测试Volatile、AtomicInteger)的相关文章

【Java并发编程】6、volatile关键字解析&amp;内存模型&amp;并发编程中三概念

转自:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来

Java多线程之synchronized和volatile的比较

概述 在做多线程并发处理时,经常需要对资源进行可见性访问和互斥同步操作.有时候,我们可能从前辈那里得知我们需要对资源进行 volatile 或是 synchronized 关键字修饰处理.可是,我们却不知道这两者之间的区别,我们无法分辨在什么时候应该使用哪一个关键字.本文就针对这个问题,展开讨论. 版权说明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 本文作者:Coding-Naga 发表日期: 2016年4月5日 本文链接:http://blog.csdn.net/

Java并发编程(三)volatile域

相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大,volatile关键字为实例域的同步訪问提供了免锁的机制.假设声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被还有一个线程并发更新的. 再讲到volatile关键字之前我们须要了解一下内存模型的相关概念以及并发编程中的三个特性:原子性,可见

JAVA多线程基础学习三:volatile关键字

Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个关键字的作用就是告诉编译器,只要是被此关键字修饰的变量都是易变的.不稳定的.那为什么是易变的呢?因为volatile所修饰的变量是直接存在于主内存中的,线程对变量的操作也是直接反映在主内存中,所以说其是易变的. 一.Java内存模型 Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理

Java并发(3)- 聊聊Volatile

引言 谈到volatile关键字,大多数开发者都有一定了解,可以说是开发者非常熟悉,深入之后又非常陌生的一个关键字.相当于轻量的synchronized,也叫轻量级锁,与synchronized相比性能上开销较少,同时又具备了可见性.有序性以及部分原子性,是Java并发需中非常重要的一个关键字.这篇文章我们将从volatile底层原理上来深入剖析他是怎么保证可见性.有序性以及部分原子性的,同时也会总结一些volatile关键字的典型应用场景. volatile的"部分"原子性 所谓原子

java自增运算

记得大学刚开始学C语言时,老师就说:自增有两种形式,分别是i++和++i,i++表示的是先赋值后加1,++i是先加1后赋值,这样理解了很多年也没出现问题,直到遇到如下代码,我才怀疑我的理解是不是错了: public class Client { public static void main(String[] args) { int count =0; for(int i=0;i<10;i++) { count=count++; } System.out.println("count=&q

Java并发(四):volatile的实现原理

synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronized volatile是一个变量修饰符,只能用来修饰变量. volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存. volatile读:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效.线程接下来将从主内存中读取共享变量. volatile实现原理 1)JMM把内存屏障指令分为下列四类: StoreLoad Barriers

JAVA 大数据内存耗用测试

JAVA 大数据内存耗用测试import java.lang.management.ManagementFactory;import java.lang.management.MemoryMXBean; public class MemoryTest { public static void main(String[] args) throws InterruptedException { int row = 50_000; int column = 20; String[] data = ne

mycat分片表全局自增主键测试

mycat分片表全局自增主键测试 一.全局序列号介绍 在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一.为此,MyCat 提供了全局 sequence,并且提供了包含本地配置和数据库配置等多种实现方式. 1.本地文件方式 使用服务器本地磁盘文件的方式 2.数据库方式 使用专用数据库的方式 3.本地时间戳方式 使用时间戳算法方式 4.分布式ZK ID 生成器 基于ZK 与本地配置的分布式ID 生成器(可以通过ZK 获取集群(机房)唯一InstanceID,也可以通过配置文 件配置