java多线程自增如何保证原子性

概述

本来以为自己对于java Volatile关键字比较熟悉的了,结局了可见性和重排序,但是不能保证的原子性。然而最近面试的时候,有个面试官手写了部分代码,问关于多线程自增的原子性问题。然后才发现自己其实也没那么熟悉,以及理解的不透彻。那就肯定得开篇文章出来说一下这个问题了,顺便巩固记忆!

问题

有代码如下:

public class A {

    public volatile int i;

    public static void main(String[] args) {
        A a = new A();
        for (int k = 0; k < 3; k++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    a.add();
                }
                a.print();
            }).start();
        }
    }

    public void add() {
        i++;
    }

    public void print() {
        System.out.println(i);
    }

}

问:输出的最大的值是否为300?
当时我看到volatile关键字以为确实是300,其实后面一想,并且关联上单例的一些多线程问题,发现确实不一定是300,至于为什么?下面直接看字节码来解答一下即可。

问题原因

首先先了解一下原子性是什么:
在这里插入图片描述

那既然非原子性的操作,多线程的情况下会有问题,那如果会产生上面的问题的话,直接看一下代码是否是原子性就好。

其实直接看我们编写的代码的话,其实就一行,那这样看起来确实是原子性的
在这里插入图片描述
但是为什么又会出现当前问题呢,这个只能看class文件里面的内容了
首先我是使用idea的,可以直接用插件 jclasslib ByteCode viewer来看当前类的字节码文件
然后看到 i++的操作其实是下面这样的,那其实很明显,获取值 -> 相加 -> 赋值这操作,其实不是原子性的,多线程的情况下有可能出现混乱的问题
在这里插入图片描述
如果正常来说,两个线程的流程是这样的:
在这里插入图片描述
这样一看,线程相加也没问题

但是由于没保证到原子性,那出现问题的流程又会是怎样的呢:
在这里插入图片描述
根据字节码以及两张流程图,其实就可以发现问题的所在,以及多线程原子性的问题。那我们该怎么解决这些问题呢?

解决方案

当时面试官说的解决方案是有多个,但是目前我只想到了两个办法,一个是加锁,如synchronized和Lock,另外一个是cas(概念没记错是线程不争抢所,大家都去操作,只有得到的值是预期值的时候,才返回相对应的值),但是cas如何实现却不太知道,所以就有点知其然不知其所以然了。

synchrnoized关键字加锁
在这里插入图片描述
当然除了加在方法中,也可以再 for循环那边进行加锁。
加锁的操作的话,除了synchronized的话,也可以使用Lock对象
Lock对象加锁
在这里插入图片描述
cas操作
cas操作的话只懂概念,但是不会怎么做,然后发现如果是多线程自增的话,其实是有对象可以直接使用的,如:AtomicInteger
此对象就是专门实现了 cas编程的一个interger类,我们可以先看示例代码
在这里插入图片描述
但是为什么使用这个类就可以实现原子性的自增呢?网上说是内部实现了cas,那只能直接从方法上看到底是实现了什么了:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后这个时候就没办法了。只能知道这个对象利用c++实现了一些cas的编程。
不过那既然是native方法,那此时其实就可以去下载 hotspot然后去下载吧:http://hg.openjdk.java.net/
省略下载步骤以及寻找步骤
主要是找unsafe这个文件名的文件就好,然后往里面找方法 compareAndSetInt,然后搜到的方法为

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj); 
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

懂c++的大神可以自己去研究一下,由于自身确实不怎么懂c++,所以也只能到这里告终了。
但是其实还是遗留了问题的,如果是自己实现cas的话,应该如何实现呢?这个确实还是只知道概念,不太清楚如何实现。如果多线程自增的话,就可以按照上面两个方法实现了,一个是加锁,一个是使用原子性的Number类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值