zoukankan      html  css  js  c++  java
  • 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并发之原子性与可见性(一)

  • 相关阅读:
    Scala入门基础1
    LitePal数据库的基本操作
    Android操作SQLate数据库
    Android广播的使用(自定义广播和本地广播)
    Android广播的使用(动态注册和静态注册)
    Android碎片的使用
    linux--硬链接与软连接
    linux下python环境的搭建
    系统时间的修改
    linux命令--文件和目录管理
  • 原文地址:https://www.cnblogs.com/zhengbin/p/5653051.html
Copyright © 2011-2022 走看看