多线程九 原子类

简介: 多线程九 原子类

JDK5提供原子类,及其操作#



AtomicXXX是通过CAS , Unsafe.compareAndSwapInt实现的

简单分析一下原子类是如果使用cas,无锁却保证线程安全的


每条线程都有自己的本地缓存,他们要想操作变量,首先是把变量复制到自己的缓存中,然后处理数据,数据处理结束后,将自己缓存的数据刷新会主存,而CAS则就是利用这一点实现的

下面是看Unsafe.class源码,可以看到,下面几个cas方法,全部是native,也就是本地方法


public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

随便找一个方法

//var1 就是方法的调用者  var2:当前的值  var4需要加的值   
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5; 
    do {
        //此方法(同样也是本地方法),返回当前 内存中var1对象里面的 var2的值
        var5 = this.getIntVolatile(var1, var2);
        //判断var1里面的值var2是否就是var5,如果相等我们更新为 var5+var4 ,否则继续循环尝试修改
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}


如何使用呢?


AtomicLong atomicLong = new AtomicLong();
 atomicLong.getAndIncrement();


解释一下,为了保证数据的安全性,根据JMM内存模型设计出了cas算法,compareAndSwap先比较,再更换值,现在看我们上面随便找的方法, 里面有一个do-while循环,其实基本上所有设及cas算法的方法,都采用了这个循环,它保证我们可以做出足够多次数的判断


  • 原子更新boolean


下面使用它完成确保某段代码只执行一次


public class demo {
// 原子更新boolean  初始化为false
public static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadPool = 200;
public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newCachedThreadPool();
    //每次两百次并发
    Semaphore semaphore = new Semaphore(threadPool);
    //计数器 执行五千次
    CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    for (int i=0;i<clientTotal;i++){
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    text();  // 我们只想执行一次的方法
                    semaphore.release();
                    countDownLatch.countDown(); //执行一次,计数减少一次
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    countDownLatch.await(); //只要计数不为零,就等待...
    executor.shutdown(); //关闭线程池
    System.out.println("atomicBoolean=="+atomicBoolean.get());
}
public static void text(){
    if (atomicBoolean.compareAndSet(false,true)){
        System.out.println("值更新了");
    }
}
}


查看结果,哪怕执行5000次,每次200的并发,if()里面的代码还是只执行了一次


值更新了
atomicBoolean==true


类名 | 描述

---|---|---

AtomicInteger | 用原子方式更新的 int 值

AtomicLong | 用原子方式更新的 long 值

AtomicBoolean|原子方式更新的 boolean 值


  • 原子更新基本类型

类名 | 描述

---|---|---

AtomicInteger | 用原子方式更新的 int 值

AtomicLong | 用原子方式更新的 long 值

AtomicBoolean|原子方式更新的 boolean 值


  • 原子更新数组

类名 | 描述 |

---|---|---

AtomicIntegerArray | 用原子方式更新其元素的 int 数组

AtomicLongArray | 用原子方式更新其元素的 long 数组


  • 原子更新抽象类型

类名 | 描述 |

---|---|---

AtomicReference | 用原子方式更新的对象引用

AtomicStamptedReference | 增加了版本号机制,解决了CAS里面的ABA问题

...

注意: 针对的是V型的对象,而不是对象里面的字段

原子更新字段


类名 描述
AtomicIntegerFieldUpdater 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新
AtomicLongFieldUpdater 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新


注意: 针对的是V型的对象,而不是对象里面的字段


JDK8 新增原子类:#


  • DoubleAdder
  • LongAdder 弥补AtomicLong在高并发情况下的性能低问题

AtomicLong里面仅仅有一个Long类型的值,多个线程并发访问,当一个线程获取到锁修改Long值时,很容易成功,但是当很多个线程并发修改它的值,很可能陷入do-while死循环,消耗性能

LongAdder里面有一组值,每个值都是一个cell,所有的cell加起来是一个base,例如Base如果是5,那么cell可能被分解成2 3,将原来修改一点的高压力分散在不同点上,多线程并发访问去抢占修改cell.在sum()方法中汇总,但线程的话,直接得出结果,从而提高性能

...


原子类并不完全安全!#



**虽然addAndGet()方法是原子的,多线程中,方法的调用是无序的, 就像下面的代码中, 原子类可以确保1个共享变量在多线程并发访问下的安全性, 但是像下面那样一个方法多条原子更新的命令就会出现意想不到的乱子



public class TextAtomicService {
    public static AtomicLong atomicLong = new AtomicLong();
    public void add(){
        System.out.println(Thread.currentThread().getName()+"加了100之后的值为:"+atomicLong.addAndGet(100));
        atomicLong.addAndGet(1);
    }
}
//
public class AtomicText extends Thread{
private  TextAtomicService textAtomicService;
public AtomicText(TextAtomicService t1){
    textAtomicService=t1;
}
@Override
public void run(){
    textAtomicService.add();
}
public static void main(String[] args) {
    TextAtomicService textAtomicService = new TextAtomicService();
    AtomicText[] atomicTexts = new AtomicText[5];
 try {
    for (int i=0;i<5;i++){
        atomicTexts[i]=new AtomicText(textAtomicService);
    }
    for (int i=0;i<5;i++){
        atomicTexts[i].start();
    }
    Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
}


可能出现这种情况,就是当前线程执行完addAndGet(100)之后,cpu的执行权被另一条线程抢走,又执行addAndGet(100),这样的结果就是200,而不是我们预想的202

解决方法就是添加Synchronized关键字,同步化!

相关文章
|
5月前
|
安全 程序员 C++
C++一分钟之-原子操作与线程安全
【6月更文挑战第27天】**C++的`std::atomic`提供线程安全的原子操作,解决多线程数据竞争。涵盖原子操作概念、应用、问题与对策。例如,用于计数器、标志位,但选择数据类型、内存顺序及操作组合需谨慎。正确使用能避免锁,提升并发性能。代码示例展示自旋锁和线程安全计数。了解并恰当运用原子操作至关重要。**
93 1
|
6月前
|
安全 Java 关系型数据库
多线程(线程安全)
多线程(线程安全)
52 4
|
6月前
|
Java 编译器
多线程(volatile)
多线程(volatile)
29 0
|
6月前
|
安全 Java
JUC并发编程之原子类
并发编程是现代计算机应用中不可或缺的一部分,而在并发编程中,处理共享资源的并发访问是一个重要的问题。为了避免多线程访问共享资源时出现竞态条件(Race Condition)等问题,Java提供了一组原子类(Atomic Classes)来支持线程安全的操作。
多线程的原子操作
多线程的原子操作
65 0
|
6月前
|
缓存 安全 Java
JUC并发编程之volatile详解
Java内存模型是Java虚拟机(JVM)规范中定义的一组规则,用于屏蔽各种硬件和操作系统的内存访问差异,保证多线程情况下程序的正确执行。Java内存模型规定了线程之间如何交互以及线程和内存之间的关系。它主要解决的问题是可见性、原子性和有序性。
|
6月前
|
安全 编译器 调度
多线程之线程安全问题
多线程之线程安全问题
|
6月前
|
前端开发
原子类的说明与使用
原子类的说明与使用 原子类是一种用于指定元素样式的基础类,它们只有一个属性:display: inline-block;,并且不包含任何内边距(padding)和外边距(margin)。原子类非常适合于构建重复使用的组件和样式。使用原子类可以提高样式的可复用性和可维护性,因为它们是单一的、独立的类,可以方便地组合和重复使用。
|
安全 Java 编译器
多线程(四):线程安全
多线程(四):线程安全
167 0
多线程(四):线程安全
【并发编程】原子类
【并发编程】原子类