您当前的位置: 首页 > 

Dongguo丶

暂无认证

  • 1浏览

    0关注

    472博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

atomic

Dongguo丶 发布时间:2021-09-24 19:25:22 ,浏览量:1

原子操作

原子(atomic)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意 为“不可被中断的一个或一系列操作”。

处理器如何实现原子操作

最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器是不能自动保证其原子性的,比如跨总线宽度、跨多个缓存行和跨页表的访问。但是,处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。

使用总线锁保证原子性

如果多个处理器同时对共享变量进行读改写操作 (i++就是经典的读改写操作),那么共享变量就会被多个处理器同时进行操作,这样读改写操 作就不是原子的,操作完之后共享变量的值会和期望的不一致。举个例子,如果i=1,我们进行 两次i++操作,我们期望的结果是3,但是有可能结果是2,

image-20210910091045867

原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入 系统内存中。那么,想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享 变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。

使用缓存锁保证原子性

频繁使用的内存会缓存在处理器的L1、L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,在Pentium 6和目前的处理器中可以使用“缓存锁定”的方式来实现复杂的原子性。所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,会修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效,当CPU1修改缓存行中的i时使用了缓存锁定,那么CPU2就不能同时缓存i的缓存行。

Java如何实现原子操作

在Java中可以通过锁和循环CAS的方式来实现原子操作。

使用循环CAS实现原子操作

JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止,

从Java 1.5开始,JDK的并发包里提供了一些类来支持原子操作,如AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。这些原子包装类还提供了有用的工具方法,比如以原子的方式将当前值自增1和自减1。

CAS实现原子操作的三大问题 在Java并发包中有一些并发框架也使用了自旋CAS的方式来实现原子操作,比如 LinkedTransferQueue类的Xfer方法。CAS虽然很高效地解决了原子操作,但是CAS仍然存在三 大问题。ABA问题,循环时间长开销大,以及只能保证一个共享变量的原子操作。

使用锁机制实现原子操作

锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁

atomic

基本类型原子类

AtomicInteger

AtomicBoolean

AtomicLong

常用API

以 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));


boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
实例

atomicInteger实现50000次的自增

使用CountDownLatch保证50个线程执行完再输出结果,不然50个线程还没执行完,主线程就退出了

就可以不用sleep了,sleep无法精确保证50个线程执行完就输出结果。正好回顾下CountDownLatch在多线程的使用

package com.dongguo.atomic;

import lombok.Getter;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-13:41
 * @description:
 */

class MyNumber {
    @Getter
    private AtomicInteger atomicInteger = new AtomicInteger();

    public void addPlusPlus() {
        atomicInteger.incrementAndGet();
    }
}

public class AtomicIntegerDemo {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        final Integer THREAD_SIZE = 50;
        CountDownLatch countDownLatch = new CountDownLatch(THREAD_SIZE);
        for (int i = 1; i  {
                try {
                    for (int j = 0; j  x * 10);
        System.out.println(result);
    }
}
运行结果
100
数组类型原子类

AtomicIntegerArray

AtomicLongArray

AtomicReferenceArray

实例
package com.dongguo.atomic;

import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-13:56
 * @description:
 */
public class AtomicIntegerArrayDemo {
    public static void main(String[] args) {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
        for (int i = 0; i  {
            spinLockDemo.myLock();
            //暂停一会儿线程
            try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
            spinLockDemo.myUnLock();
        },"A").start();
        //暂停一会儿线程,保证A线程先于B线程启动并完成
        try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"B").start();

    }
}
AtomicStampedReference

携带版本号的引用类型原子类,可以解决CAS中的ABA问题

让我们知道引用变量中途被更改了几次

package com.dongguo.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-12:48
 * @description:
 */
public class ABADemo {
    public static void main(String[] args) {
        AtomicStampedReference atomicStampedReference = new AtomicStampedReference(10, 1);
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//首次版本号
            System.out.println(Thread.currentThread().getName() + "   1首次版本号" + stamp + "值为" + atomicStampedReference.getReference());
            //保证t1、t2得到相同版本号
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(10, 100, stamp, stamp + 1);
            stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "   2次版本号" + stamp + "值为" + atomicStampedReference.getReference());
            atomicStampedReference.compareAndSet(100, 10, stamp, stamp + 1);
            stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "   3次版本号" + stamp + "值为" + atomicStampedReference.getReference());
        }, "t1").start();
        //保证t1发生在t2之前
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//首次版本号
            System.out.println(Thread.currentThread().getName() + "   1首次版本号" + stamp + "值为" + atomicStampedReference.getReference());
            //保证t1、t2得到相同版本号  并且t1发生ABA问题
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(10, 123, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "修改结果为" + result + " 值为" + atomicStampedReference.getReference());
        }, "t2").start();
    }
}
AtomicMarkableReference

原子更新带有标记位的引用类型对象

它的定义就是将状态戳简化为true|false 只能解决一次性问题

不建议使用AtomicMarkableReference解决ABA问题

它不关心引用变量更改过几次,只关心是否更改过

package com.dongguo.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-14:14
 * @description:
 */
public class AtomicMarkableReferenceDemo {
    public static void main(String[] args) {
        AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(1,false);

        new Thread(()->{
            boolean marked = atomicMarkableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"修改标志为"+marked);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicMarkableReference.compareAndSet(1,100,marked,!marked);
            System.out.println("值为"+atomicMarkableReference.getReference());
        },"t1").start();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            boolean marked = atomicMarkableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"修改标志为"+marked);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicMarkableReference.compareAndSet(1, 123, marked, !marked);
            System.out.println("修改结果"+result);
            System.out.println(Thread.currentThread().getName()+"修改标志为"+atomicMarkableReference.isMarked());
            System.out.println("值为"+atomicMarkableReference.getReference());
        },"t2").start();
    }
}
t1修改标志为false
t2修改标志为false
值为100
修改结果false
t2修改标志为true
值为100
对象的属性修改原子类(字段更新器)

**AtomicIntegerFieldUpdater **原子更新对象中int类型字段的值

AtomicLongFieldUpdater原子更新对象中Long类型字段的值

AtomicReferenceFieldUpdater原子更新引用类型字段的值

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

Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type

作用:以一种线程安全的方式操作非线程安全对象内的某些字段

如果使用锁,需要锁定整个对象

synchronized(obj){
	//需要锁定整个对象
}

比如银行卡,有卡号、开户地址、户主、电话、余额等属性,只有余额是经常变化的

我们可以使用对象的属性修改原子类 ,不用锁定整个对象,只锁定敏感性变化的某一个字段。

达到精确加锁,细粒度处理,节约内存的目的

要求:

1更新的对象属性必须使用 public volatile 修饰符。

2因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

AtomicIntegerFieldUpdater
package com.dongguo.atomic;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-14:41
 * 以一种线程安全的方式操作非线程安全对象的某些字段。
 * 需求:
 * 1000个人同时向一个账号转账一元钱,那么累计应该增加1000元,
 * 除了synchronized和CAS,还可以使用AtomicIntegerFieldUpdater来实现。
 */
class BankAccount {
    private String bankName = "ACBC";
    public volatile int money = 0;
    AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");

    public void transferMoney(BankAccount bankAccount) {
        fieldUpdater.incrementAndGet(bankAccount);
    }
}

public class AtomicIntegerFieldUpdaterDemo {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount();

        for (int i = 1; i  {
                bankAccount.transferMoney(bankAccount);
            }, String.valueOf(i)).start();
        }
        //暂停毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(bankAccount.money);
    }
}
运行结果
1000

扩展:

很多开源框架中,例如Netty中好多地方都用到了AtomicIntegerFieldUpdater。

比如AbstractReferenceCountedByteBuf 类中定义了一个refCntUpdater:

    private static final AtomicIntegerFieldUpdater refCntUpdater;

    static {
        AtomicIntegerFieldUpdater updater =
                PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
        if (updater == null) {
            updater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
        }
        refCntUpdater = updater;
    }

refCntUpdater 是Netty用来记录ByteBuf被引用的次数,会出现并发的操作,比如增加一个引用关系,减少一个引用关系,其retain方法,实现了refCntUpdater的自增:

    private ByteBuf retain0(int increment) {
        for (;;) {
            int refCnt = this.refCnt;
            final int nextCnt = refCnt + increment;

            // Ensure we not resurrect (which means the refCnt was 0) and also that we encountered an overflow.
            if (nextCnt  {
            return x + y;
        }, 0);
        accumulator.accumulate(1);
        accumulator.accumulate(2);
        accumulator.accumulate(3);

        System.out.println(accumulator.longValue());
    }
}
运行结果
6
package com.dongguo.atomic;

import java.util.concurrent.atomic.LongAccumulator;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-15:27
 * @description: 累减操作
 */
public class LongAccumulatorAPIDemo {
    public static void main(String[] args) {
        LongAccumulator accumulator = new LongAccumulator((x, y) -> {
            return x - y;
        }, 100);
        accumulator.accumulate(1);
        accumulator.accumulate(2);
        accumulator.accumulate(3);

        System.out.println(accumulator.longValue());
    }
}
运行结果
94
synchronized、atomicLong、LongAdder 与 LongAccumulator性能对比

50个线程,每个线程100W次,算出总点赞数出来,对比性能。

package com.dongguo.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

/**
 * @author Dongguo
 * @date 2021/9/7 0007-16:37
 * @description: 50个线程,每个线程100W次,总点赞数
 */
class ClickNumber {
    int number = 0;

    //使用synchronized
    public synchronized void add_Synchronized() {
        number++;
    }
    //atomicInteger
    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void add_atomicInteger() {
        atomicInteger.incrementAndGet();
    }

    //atomicLong
    AtomicLong atomicLong = new AtomicLong(0);

    public void add_AtomicLong() {
        atomicLong.decrementAndGet();
    }

    //longAdder
    LongAdder longAdder = new LongAdder();

    public void add_LongAdder() {
        longAdder.increment();
    }

    //longAccumulator
    LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void add_LongAccumulator() {
        longAccumulator.accumulate(1);
    }
}

public class ClickNumberDemo {
    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();

        long startTime;
        long endTime;
        CountDownLatch countDownLatch = new CountDownLatch(50);
        CountDownLatch countDownLatch2 = new CountDownLatch(50);
        CountDownLatch countDownLatch3 = new CountDownLatch(50);
        CountDownLatch countDownLatch4 = new CountDownLatch(50);
        CountDownLatch countDownLatch5 = new CountDownLatch(50);

        startTime = System.currentTimeMillis();
        for (int i = 1; i  {
                try
                {
                    for (int j = 1; j             
关注
打赏
1638062488
查看更多评论
0.0477s