JUC--001--Atomic-1

AtomicBoolean
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
public class AtomicBoolean implements java.io.Serializable 
{
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicBoolean.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    ....
}

valueOffset 是偏移量。
AtomicBoolean 创建对象以后,JVM 要为他分配内存空间用于保存 AtomicBoolean 的成员变
量。其中一个 int 类型的成员变量 value, 他相对于 AtomicBoolean 整个内存块起始地址
的偏移量就是 valueOffset。使用 AtomicBoolean 的内存地址 + 这个偏移量就是 value 
变量的内存地址。 在jvm上使用 这个偏移量也能获取 和 修改 value 的值。

所以现在操作 value 的方式就有两种了,直接操作 value, 或者通过内存地址操作它。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
直接操作:
public final boolean get() { return value != 0; }
public final void set(boolean newValue) { value = newValue ? 1 : 0; }

间接操作(通过内存地址操作)
compareAndSet
weakCompareAndSet
lazySet
getAndSet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compareAndSet 和 weakCompareAndSet 的实现在 jdk8 中是一样的,据说在 jdk9 不同。
二者都是原子的设置值。是由底层保证线程安全的。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lazySet : 这个也是设置值,也是线程安全的,但是有一个小问题。 它修改值以后其他
线程不是立刻就能知道新值的。他的作用是一个小小的优化。

如果我们加锁的方式修改一个变量: 
      【 加锁 修改  解锁并通知改变 】
      
那么本类的 set 方法就是: 
      【修改  通知改变】 因为value有volatile, 直接修改会有通知,但非线程安全
      
而 lazySet 方法就是:
      【加锁 修改  解锁】因为通过内存地址修改的 value(忽略了 volatile),  没有通知

lazySet 是一个小优化,如果一个原子操作改变,其他线程不在乎它有没有改变,
或者晚一点知道最新的值也没关系,就可以用这个
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
getAndSet: 设置值,并且返回本次设置成功时的前值。
比如想把值设置成 true,  
  如果现在就是 true, 设置成功,并且返回前值 也是 true
  如果现在是false, 设置成功,返回前值 false, 现值是 true
  运行到  --------------Flag 时得到的是前值是 true。 然后其他线程线程抢先把当前值
          改成了 false。 接着运行 --------------Flag 后的代码, 即希望当前是 true
          并把他改成 true。 结果修改失败,因为期望的当前值是 false 了, 于是再循环
          一次, 然后重新获取 prev 的值, 这次是 false,  接着 期望当前是 false
          并把它改成 true. 成功了。 当前值改成了 true, 并返回修改成功前的值即 false


    public final boolean getAndSet(boolean newValue) {
        boolean prev;
        do {
            prev = get();
            //--------------Flag
        } while (!compareAndSet(prev, newValue));
        return prev;
    }

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AtomicInteger
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
和前面的 AtomicBoolean 有很大相似性:
内部也是一个 int 类型的 value, 还有 value 的内存偏移量。
操作也同样有两种,直接操作 和 简接操作
set             非线程安全的
lazySet         最终设置成功,但是其他线程不是立刻可见刚设置的最新值
其他的类似 ++i  i++   --i   i--   i += delta   i-=detla 功能的方法  都是线程安全的

compareAndSet 和 weakCompareAndSet jdk8 底层实现一样, 把期望值改成现值,成功与否?

下面4个是 jdk8 才出来的
getAndUpdate(IntUnaryOperator updateFunction)
updateAndGet(IntUnaryOperator updateFunction)
getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)
accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)
他们的参数有一个是 function。 
他们的返回值根据方法名的语义可知,内部都使用CAS确保一定更新成功

getAndUpdate 和 updateAndGet 是将 当前值传递给 这个 function, 这个function 经过
处理后返回一个新值,用这个新值更新旧值。
比如,想要把 现在的值计算一下平方, 传入 lambda 表达式完成要求
AtomicInteger num = new AtomicInteger(5);
int i1 = num.updateAndGet((i) -> i * i);    成功后现值 25

getAndAccumulate 和 accumulateAndGet 他们的实现一样,就是返回值不同。
两个参数,x 是要被更新的值。 第二个参数也是一个 funtion, 这个 function 也
有2个参数的, 第一个参数是本类的前值, 第二个参数是 就是 x。 我们自定义
一个方法传入,返回 本类的前值和x 的某种运算结果, 这个结果作为原子类的新值

比如想要现在的值 更新为 2被, 并加上一个常数100.

AtomicInteger num = new AtomicInteger(5);
int i1 = num.accumulateAndGet(100, (prev, x) -> prev * 2 + x ); //成功后现值 110
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AtomicIntegerArray
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
对int数组的原子操作:

它内部的四个成员变量:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;

array : 保存数据的普通数组
shift : 某个数组元素相对数组首元素偏移位?  不太理解, 后面再说
base : 数组 array 这个变量相对于这个类内存起始地址的偏移量。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
unsafe.arrayBaseOffset(int[].class); 这个方法用于获取数组类型变量相对类的内存偏移
unsafe.arrayIndexScale(int[].class); 这个方法用于获取一个数组元素占用的内存大小。
  比如 int 类型的数组元素,占用数组内存为 4, boolean 类型数组元素 为1, long 是 8
  String 类型的数组元素 占用的数组内存还是 4。 因为数组保存的是引用,不是String本身。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
现在就想要测试一下 Unsafe 的方法。
结果个人代码无法获取 Unsafe 对象【Unsafe unsafe = Unsafe.getUnsafe();】
会抛出 安全异常。

可以通过反射的方式获取:
方式1: 反射 Unsafe 的私有构造方法

    private static void t6_intArr() throws Exception {
        Class<Unsafe> unsafeClass = Unsafe.class;
        Constructor<Unsafe> constructor = unsafeClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        Unsafe unsafe = constructor.newInstance();
        System.out.println(unsafe.arrayIndexScale(int[].class));
    }

方式2: 反射 Unsafe 中的变量私有静态常量 theUnsafe

    private static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe)field.get(null);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

方式3:反射 AtomicIntegerArray 类中的 Unsafe 属性对象(产生额外对象)

    private static void t4_intArr() throws Exception {
        AtomicIntegerArray array = new AtomicIntegerArray(2);
        Class<? extends AtomicIntegerArray> aClass = array.getClass();
        Field unsafe = aClass.getDeclaredField("unsafe");
        unsafe.setAccessible(true);
        Unsafe unsafe1 = (Unsafe) unsafe.get(null);
    }

现在有 Unsafe 就能测试它的方法了。 建议使用 方式2, Unsafe 设计本就是单列的
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AtomicIntegerArray 方法:
    //参数是元素所在数组的下标
    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    //base 是数组相对本类的内存偏移量, i << shift 是 第i个元素相对数组的偏移
    //返回值是 下标为 i 的元素相对本类的内存偏移,有这个就能直接内存访问这个元素。
    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
set          非线程安全
lazySet      线程安全,但是其他线程不能立刻可见

线程安全的:
getAndSet
compareAndSet
weakCompareAndSet  jdk8 的实现和 compareAndSet  一样

还有对某个元素的 ++ -- 等操作, 也是原子的

getAndUpdate(int i, IntUnaryOperator updateFunction)
updateAndGet(int i, IntUnaryOperator updateFunction)
getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction)
accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction)

和 AtomicInteger 的那4个方法功能一样, 只不过 AtomicInteger 是对内部 int 的操作
AtomicIntegerArray 是对内部 int 数组的某个元素操作, 参数 i 是该元素的下标
这四个方法也是原子操作。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AtomicIntegerFieldUpdater
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
这个类是把某个对象(比如 X 对象)中的int类型的属性做一层封装。
然后对 AtomicIntegerFieldUpdater 的操作实际上是对 X 对象的 某个int 属性操作。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
比如有一个计数器类 定义

public class MyCount
{
    //必须是 volatile 修饰的 int 类型的
    public volatile int count = 0;
}

MyCount 类中的 count 属性肯定是线程不安全的,如果在多线程中使用,代码同步是少不了的。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
使用 AtomicIntegerFieldUpdater 来包装 MyCount 的 count 属性, 实现原子操作。

private static void t9_intField() throws InterruptedException {
    int nThread = 3;
    MyCount myCount = new MyCount();
    AtomicIntegerFieldUpdater<MyCount> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(MyCount.class, "count");
    CountDownLatch countDownLatch = new CountDownLatch(nThread);
    long start = System.nanoTime();

    for (int nt = 0; nt < nThread; ++nt) {
        new Thread(() -> {
            for (int i=0; i < 100000000; ++i) {
                fieldUpdater.incrementAndGet(myCount);

                //线程不安全
                //myCount.count++;

                //同步,效率低一点点
                //synchronized (Application.class) { myCount.count++; }
            }
            countDownLatch.countDown();
        }).start();
    }

    countDownLatch.await();
    long take = System.nanoTime() - start;
    System.out.println("耗时:" + TimeUnit.NANOSECONDS.toMillis(take));

    System.out.println(myCount.count);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
想要使用 AtomicIntegerFieldUpdater ,那就要本类有访问 MyCount 的权限。
所以 MyCount 类是要满足这些权限要求的。比如 只能是 int 类型的成员变量,
必须使用 volatile 修饰, 访问权限是 public , 尝试权限改小,但会抛异常。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
然后对 AtomicIntegerFieldUpdater 对象的访问 就是对 MyCount::count 的访问。
其他的内容和 前面的 AtomicInteger 类没什么区别了
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AtomicLong
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AtomicLongArray
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AtomicLongFieldUpdater
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
这三个和 前面的差不多, 不再多说。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值