JUC下的原子类(Atomic)

1 原子整数

JUC并发包提供了:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

以AtomicInteger为例

AtomicInteger i = new AtomicInteger(0);
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));

2 原子引用

为什么需要原子引用类型?

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

2.1 AtomicReference

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。

public class AtomicReference_P186 {
    public static AtomicReference<User> atomicUserReference = new AtomicReference<User>();

    public static void main(String[] args) {
        User user = new User("cocan",15);
        atomicUserReference.set(user);
        User updateUser = new User("shinichi",17);
        atomicUserReference.compareAndSet(user,updateUser);
        System.out.println(atomicUserReference.get().getName());
        System.out.println(atomicUserReference.get().getOld());
    }

    static class User{
        private String name;
        private int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public int getOld() {
            return old;
        }
    }
}

主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程
希望:只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号

这时候就需要使用AtomicStampedReference

2.2 AtomicStampedReference

AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。

public class atomicStampedReference {
    static AtomicStampedReference<String> ref = new AtomicStampedReference<String>("A", 0);

    public static void main(String[] args) throws Exception {
        System.out.println("main start...");
        //获取A
        //这个共享变量是否被其他线程修改过
        String prev = ref.getReference();
        //版本号
        int version = ref.getStamp();
        System.out.println("["+Thread.currentThread().getName()+"]"+version);

        other();
        Thread.sleep(100);
        //尝试改为C
        System.out.println("["+Thread.currentThread().getName()+"]"+"change A -> C " + ref.compareAndSet(prev, "C", version, version + 1));
    }

    private static void other() throws Exception {
        new Thread(() -> {
            int stamp = ref.getStamp();
            System.out.println("["+Thread.currentThread().getName()+"]"+stamp);
            System.out.println("["+Thread.currentThread().getName()+"]"+"change A -> B "+ ref.compareAndSet(ref.getReference(),"B",stamp,stamp+1));
        },"t1").start();
        Thread.sleep(1);
        new Thread(() -> {
            int stamp = ref.getStamp();
            System.out.println("["+Thread.currentThread().getName()+"]"+stamp);
            System.out.println("["+Thread.currentThread().getName()+"]"+"change A -> B "+ ref.compareAndSet(ref.getReference(),"A",stamp,stamp+1));
        },"t2").start();
    }
}

2.3 AtomicMarkableReference

但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference
在这里插入图片描述

public class atomicMarkableReference {
    public static void main(String[] args) throws Exception{
        GarbageBag bag  = new GarbageBag("装满了垃圾");
        //参数2 mark可以看做一个标记,表示垃圾袋满了
        AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag,true);

        System.out.println("start...");
        GarbageBag prev = ref.getReference();
        System.out.println(prev.toString());

        new Thread( () -> {
            System.out.println("start...");
            bag.setDesc("空垃圾袋");
            ref.compareAndSet(bag,bag,true,false);
            System.out.println(bag.toString());

        },"保洁阿姨").start();

        Thread.sleep(5000);
        System.out.println("想换一只新垃圾袋?");
        boolean success = ref.compareAndSet(prev,new GarbageBag("空垃圾袋"),true,false);
        System.out.println("换了吗?"+success);
        System.out.println(ref.getReference().toString());
    }
}

class GarbageBag {
    String desc;

    public GarbageBag(String desc) {
        this.desc = desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    @Override
    public String toString() {
        return super.toString() + " " + desc;
    }
}

3 原子数组

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
public class atomicIntegerArray {
    static int[] value = new int[]{1, 2};

    static AtomicIntegerArray ai = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            int prev = ai.get(0);
            while(true){
                if (ai.compareAndSet(0, prev,i)){
                    break;
                }
            }
        }
//        for (int i = 0; i < 100; i++) {
//            new Thread(() -> {
//               value[0]--;
//            }).start();
//        }
        System.out.println(ai.get(0));
//        System.out.println(value[0]);
//        ai.getAndSet(0,3);
//        System.out.println(ai.get(0));
//        System.out.println(value[0]);
    }
}

4 字段更新器

  • AtomicReferenceFieldUpdater // 域 字段
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常

public class atomicFieldUpdater {
    public static void main(String[] args) {
        Student stu = new Student();
        AtomicReferenceFieldUpdater updater =
                AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");

        System.out.println(updater.compareAndSet(stu, null, "张三"));
        System.out.println(stu);
    }
}

class Student{
    volatile String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

5 原子累加器

5.1 累加器性能比较

public class atomicAccumulator {
    public static void main(String[] args) {
        demo(
                () -> new AtomicLong(0),
                (adder) -> adder.getAndIncrement()
        );

        demo(
                () -> new LongAdder(),
                (adder) -> adder.increment()
        );
    }

    /**
     * () -> 结果  提供累加器对象
     * (参数) ->  执行累加操作
     */
    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action){
        T adder = adderSupplier.get();
        List<Thread> ts = new ArrayList<>();
        //4个线程,每人累加50万
        for (int i = 0; i < 4; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action. accept(adder);
                }
            }));
        }

        long start = System.nanoTime();
        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end  = System.nanoTime();
        System.out.println(adder+"cost:"+(end - start)/1000_000);

    }
}
1000000 cost:43 
1000000 cost:9 
1000000 cost:7 
1000000 cost:7 
1000000 cost:7 
1000000 cost:31 
1000000 cost:27 
1000000 cost:28 
1000000 cost:24 
1000000 cost:22

性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。

5.2 源码之LongAdder

LongAdder类有几个关键域

//累加单元数组,惰性初始化
transient volatile Cell[] cells;

//基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;

//在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;

5.3 cas锁

不能用于实践

public class lockCas {
    //0 没加锁
    //1 加锁
    private AtomicInteger state = new AtomicInteger(0);

    public void lock() {
        while (true) {
            if (state.compareAndSet(0, 1)) {
                break;
            }
        }
    }

    public void unlock() {
        System.out.println("["+Thread.currentThread()+"]"+"unlock...");
        state.set(0);
    }

    public static void main(String[] args) {
        lockCas lock = new lockCas();
        new Thread(() -> {
            System.out.println("["+Thread.currentThread()+"]"+"begin...");
            lock.lock();
            try {
                System.out.println("["+Thread.currentThread()+"]"+"lock");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();

        new Thread(() -> {
            System.out.println("["+Thread.currentThread()+"]"+"begin...");
            lock.lock();
            try {
                System.out.println("["+Thread.currentThread()+"]"+"lock...");
            } finally {
                lock.unlock();
            }
        }).start();
    }
}

5.4 原理之伪共享

其中 Cell 即为累加单元

// 防止缓存行伪共享
@sun.misc.Contended
static final class Cell {
	 volatile long value;
	 Cell(long x) { value = x; }
	 
	 // 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值
	 final boolean cas(long prev, long next) {
	 	return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
	 }
	 // 省略不重要代码
}

缓存与内存速度比较
在这里插入图片描述

从cpu到大约需要的时钟周期
寄存器1 cycle(4Hz的CPU约为0.25ms)
L13-4cycle
L210-20cycle
L340-45cycle
内存120-240cycle

因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。

缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)

缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中

CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效

在这里插入图片描述

因为 Cell 是数组形式,在内存中是连续存储的,一个 Cell 为 24 字节(16 字节的对象头和 8 字节的 value),因此缓存行可以存下 2 个的 Cell 对象。这样问题来了:
Core-0 要修改 Cell[0]
Core-1 要修改 Cell[1]
无论谁修改成功,都会导致对方 Core 的缓存行失效,比如 Core-0 中 Cell[0]=6000, Cell[1]=8000 要累加
Cell[0]=6001, Cell[1]=8000 ,这时会让 Core-1 的缓存行失效

@sun.misc.Contended 用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的空白,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
在这里插入图片描述

5.5 LongAdder源码-add

public void add(long x) {
	 // as 为累加单元数组
	 // b 为基础值
	 // x 为累加值
	 Cell[] as; long b, v; int m; Cell a;
	 // 进入 if 的两个条件
	 // 1. as 有值, 表示已经发生过竞争, 进入 if
	 // 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 if
	 if ((as = cells) != null || !casBase(b = base, b + x)) {
		 // uncontended 表示 cell 没有竞争
		 boolean uncontended = true;
		 if (
			 // as 还没有创建
			 as == null || (m = as.length - 1) < 0 ||
			 // 当前线程对应的 cell 还没有
			 (a = as[getProbe() & m]) == null ||
			 // cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )
			 !(uncontended = a.cas(v = a.value, v + x))
			 ) {
				 // 进入 cell 数组创建、cell 创建的流程
				 longAccumulate(x, null, uncontended);
			 }
	 }
}

add 流程图
在这里插入图片描述

6 Unsafe

6.1 概述

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得

public class unsafe {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    }
}

6.2 Unsafe CAS操作

public class unsafe {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        System.out.println(unsafe);

        //1.获取域的偏移地址
        long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
        long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));

        Teacher t = new Teacher();
        //2.执行cas操作
        unsafe.compareAndSwapInt(t,idOffset,0,1);
        unsafe.compareAndSwapObject(t,nameOffset,null,"张三");

        //3.验证结果
        System.out.println(t);
    }
}

class Teacher{
    volatile int id;
    volatile String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
//输出
sun.misc.Unsafe@4554617c
Teacher{id=1, name='张三'}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

似梦的苏烟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值