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

java多线程自增如何保证原子性,第1张

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类

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5708466.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存