AtomicInteger类和int以及i++的线程安全问题

问题:i++是线程安全的吗?

这个问题可以从两个方面回答

  • 若是局部变量,那么i++是线程安全;
  • 若是全局变量,那么i++非线程安全。

原因:

  • 若是局部变量,那其他线程也访问不到,所以根本不存在是否安全这个问题。
  • 若是全局变量,任意线程都可以访问,而i++这个操作是非原子性的,这个会编译成 i = i +1;这里做了多个操作,包括 读取,修改,写入 。并发情况下会出现访问冲突。
    在这里插入图片描述
    举个例子:
    比如有200个线程同时执行i++操作,会存在比如同时两个线程当前获得i的值是10,然后同时执行++操作,都执行完后,最后得到的是11,所以线程不安全。

int自增实验

测试实例:

   /**
     * 全局共享变量
     */
    public static int count = 0;
  
   /**
     * 测试
     * count ++
     * 期望结果: 2*10000=20000
     * 实际结果:总小于 20000
     */
    @Test
    public void testInt() throws Exception {
        //创建线程池
        ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(200);
        for (int i = 0; i < 10000; i++) {
            scheduler.execute(() -> {
                for (int j = 0; j < 2; j++) {
                    System.out.println("线程:" + Thread.currentThread().getName() + " count=" + count++);
                }
            });
        }
        scheduler.shutdown();
        Thread.sleep(1000);
        System.out.println("最终结果是 :" + count);
    }

测试结果

由此可见,这种自增方式是线程不安全的,怎么解决呢?
第一个想到的就是加锁synchronized或者Lock,这里暂不介绍加锁的方式。
使用了锁去做计数的话系统的性能将会大大下降,有没有更优雅的方式呢?
这里还可以使用 AtomicInteger

AtomicInteger 自增

测试实例

   /**
     * 全局共享变量
     */
    private static AtomicInteger atomicInteger = new AtomicInteger(0);
   /**
     * 测试
     * atomicInteger ++
     * 期望结果: 2*10000=20000
     * 实际结果: 20000
     */
    @Test
    public void testAtomicInteger() throws Exception {
        //创建线程池
        ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(200);
        for (int i = 0; i < 10000; i++) {
            scheduler.execute(() -> {
                for (int j = 0; j < 2; j++) {
                    //自增并返回当前值
                    int andIncrement = atomicInteger.incrementAndGet();
                    System.out.println("线程:" + Thread.currentThread().getName() + " count=" + andIncrement);
                }
            });
        }
        scheduler.shutdown();
        Thread.sleep(1000);
        System.out.println("最终结果是 :" + atomicInteger.get());
    }

测试结果
在这里插入图片描述

原因分析
源码

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

AtomicInteger类中变量valueOffset,用来记录AtomicInteger类中value的内存位置 。
当需要变量的值改变的时候,先通过get()得到valueOffset位置的值,也即当前value的值.给该值进行增加,并赋给next

巧妙之处!
compareAndSet()比较之前取到的value的值当前有没有改变,若没有改变说明没有被其它线程修改,就将next的值赋给value,倘若和之前的值相比的话发生变化的话,则重新一次循环,直到存取成功,通过这样的方式能够保证该变量是线程安全的

优点总结:

  • 线程安全,因为AtomicInteger由硬件提供原子操作指令实现的
  • 在非激烈竞争的情况下,开销更小,速度更快。
  • 使用非阻塞算法来实现并发控制的,可以避免多线程的优先级倒置和死锁情况的发生,提升在高并发处理下的性能。

缺点总结

  • AtomicInteger,如果在线程较多的情况下,效率会变的很低,因为没有加锁,其他线程会频繁打断存取的过程,导致较低
  • 使用一定要注意越界问题

AtomicInteger 越界问题

现象:当AtomicInteger增加到了2147483647(Integer.MAX_VALUE)再加一,值会变成负数-2147483648(Integer.MIN_VALUE)。当资源数目不断累积超过最大值变成负数的时候,最后产生的值中会带有一个“-”

测试实例

 private static AtomicInteger atomicInteger = new AtomicInteger(Integer.MAX_VALUE);
    public static void main(String[] args) {
        System.out.println("初始值 : " + atomicInteger);
        for (int i = 0; i < 10; i++) {
            System.out.println("当前值 : "+ atomicInteger.incrementAndGet());
        }
    }

测试结果
在这里插入图片描述

使用建议

在高并发以及微服务项目中,技术操作建议使用 Redis 计数器。缓存操作速度快,单线程无竞争。

  1. AtomicInteger常用接口
    public final int get() //获取当前的值
    public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    public final int getAndIncrement()//获取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
    public final int incrementAndGet() //获取自增后的值
    public final int decrementAndGet()//获取自减后的值

2.Java的原始数据类型(primitive datatypes),
如short、int、double、long、boolean这些是非线程安全的;
3.Java自带的线程安全的基本类型包括:
AtomicInteger, AtomicLong, AtomicBoolean, AtomicIntegerArray,AtomicLongArray等

猜你喜欢

转载自blog.csdn.net/qq_38011415/article/details/88857201
今日推荐