我们发现在不做任何同步的情况下,我们计算的累加结果是错误的。

com.mmall.concurrency.example.count.CountExample2

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\count\CountExample2.java

package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger; @Slf4j
@ThreadSafe
public class CountExample2 { // 请求总数
public static int clientTotal = 5000;//1000个请求 // 同时并发执行的线程数
public static int threadTotal = 200;//允许并发的线程数是50 public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(()-> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
//log.info("count:{}",count);
log.info("count:{}",count.get()); } private static void add() {
//count++;
count.incrementAndGet();
// count.getAndIncrement();
}
}

把计数的值从int换成了AtomicInteger,看一下AtomicInteger的incrementAndGet方法的源码实现。AtomicInteger的源码实现使用了一个unsafe的类,unsafe提供了一个方法叫做getAndAddInt,getAndAddInt这个方法不是特别关键的,核心关键是getAndAddInt它的实现。

compareAndSwapInt这个方法需要大家特别注意。compareAndSwapInt它其实是用native标识的方法。native这个代表是Java底层的方法,不是我们通过Java去实现的。Object var1是传过来的count这个对象,第二个值long var2是我们当前的值,比如我想执行的是2+1=3这个操作,当前这个第二个参数var2它等于2,第三个值var4等于1,如果当前的值var2跟底层的这个值var5相同的话,那么把它var5更新成后面的这个值va5+var4。当我们一个方法进来的时候,long var2它这个值是2,这个时候我们第一次取出来的变量var5的值应该也等于2,但是当我们在执行进行更新层增删的时候呢,可能会被别的线程更改,因此它这里要判断如果我当前这个值var2跟我期望的值var5是相同的话,就是这里面取出来的值var2也等于2的时候,那么我才允许它更新成3,否则重新取出来当前这个变量var5,然后这里面的变量var2相当于是重新从我当前的这个count对象里面取一次也会变成3,相当于继续判断,如果当前的值var2=3,与我底层的值var5相同也等于3的话,那么我就把它变成3+1=4的这个值。通过这样不停地循环来判断,最后保证的是,期望的是一个值,与底层的值完全相同的时候,才执行对应的+1的这个值,把底层的值给覆盖掉。

compareAndSwapInt这个方法的核心就是CAS的核心。刚才我们介绍的是整型值的处理,对于其他的类型比如long、double、对象的值它们都可以通过对应的方法来进行处理,核心都是通过compareAndSwap的方式来进行操作。

关于CAS的实践我们就先讲到这里,大家一定要记住这里面它的实现的原理,是拿当前这个对象里的值var2和底层的值var5来进行对比,如果当前的值跟底层的值它们一样的时候,才执行对应的操作,这就是它的核心的原理。如果不一样的话那就不停地取最新的值,直到它们相同的时候才进行这个操作。如果大家不明白为什么这个count对象里面的值会和正常存储数据的底层的值不一样的话,大家可以看一下工作内存和主内存的关系,count里面存的值其实就属于工作内存,底层其实就是主内存,它们的值不一定完全一样的,所以我们做一些同步的操作才能保证它们在某个时刻是一样的。

com.mmall.concurrency.example.atomic.AtomicExample1

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\atomic\AtomicExample1.java

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger; @Slf4j
@ThreadSafe
public class AtomicExample1 { // 请求总数
public static int clientTotal = 5000;//1000个请求 // 同时并发执行的线程数
public static int threadTotal = 200;//允许并发的线程数是50 public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(()-> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
//log.info("count:{}",count);
log.info("count:{}",count.get()); } private static void add() {
//count++;
count.incrementAndGet();
// count.getAndIncrement();
}
}

com.mmall.concurrency.example.atomic.AtomicExample2

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\atomic\AtomicExample2.java

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; @Slf4j
@ThreadSafe
public class AtomicExample2 { // 请求总数
public static int clientTotal = 5000;//1000个请求 // 同时并发执行的线程数
public static int threadTotal = 200;//允许并发的线程数是50 public static AtomicLong count = new AtomicLong(0); public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(()-> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
//log.info("count:{}",count);
log.info("count:{}",count.get()); } private static void add() {
//count++;
count.incrementAndGet();
// count.getAndIncrement();
}
}

JDK8里面单独新增了一个类LongAdder跟AtomicLong特别像,

  因此我们这个线程安全了是没问题了。为什么有了AtomicLong之后还要新增一个LongAdder这个类?很显然如果增加一个类肯定是有它的优点的。这里LongAdder它的实现我们就不多说了,我们具体说一下LongAdder和AtomicLong它俩对应的优点和缺点。之前我们在讲AtomicInteger的实现机制的时候我们看过CAS的底层实现,它们是在一个死循环内不断地尝试修改目标值直到修改成功。如果竞争不激烈的时候呢,它修改成功的概率很高,否则的话修改失败的概率就会很高,在大量修改失败的时候呢,这些原子操作就会进行多次的循环尝试,因此呢性能会受到一些影响。这里呢有一个知识点,对于普通类型的long和double变量,JVM允许将64位的读操作或者写操作拆成两个32位的操作,那么LongAdder这个类它的实现是基于什么思想呢?它的核心是将热点索引分离,比如说它可以将AtomicLong的内部核心数据value分离成一个数组,每个线程访问时通过哈希系统算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点数据value它会被分离成多个单元的cell,每个cell独自维护内部的值,当前对象的实际值由所有的cell累计合成,这样的话热点就进行了有效的分离并提高了并行度,这样一来呢LongAdder它相当于是在AtomicLong的基础上将单点的更新压力分散到各个节点上,在第一并发的时候呢通过对base的直接更新,可以很好的保障和Atomic的性能基本一致,而在高并发的时候呢则通过分散提高了性能,这就是我们所说的LongAdder,但是LongAdder也有自己的缺陷,它的缺点是在统计的时候如果有并发更新,可能会导致统计的数据有些误差。实际使用中,在处理高并发计数的时候,我们可以优先使用LongAdder,而不是继续使用AtomicLong,当然了在线程竞争很低的情况下进行计数,使用Atomic还是更简单、更直接一些,并且效率也会稍微高一点点。其他的情况下,比如序列号生成啦,这种情况下需要准确的数值,全局唯一的AtomicLong才是正确的选择,这个时候就不适合使用LongAdder了。

com.mmall.concurrency.example.atomic.AtomicExample3

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\atomic\AtomicExample3.java

package com.mmall.concurrency.example.atomic;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder; @Slf4j
@ThreadSafe
public class AtomicExample3 { // 请求总数
public static int clientTotal = 5000;//1000个请求 // 同时并发执行的线程数
public static int threadTotal = 200;//允许并发的线程数是50 public static LongAdder count = new LongAdder(); public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(()-> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count);
//log.info("count:{}",count.get()); } private static void add() {
//count++;
count.increment();
//count.incrementAndGet();
// count.getAndIncrement();
}
}

AtomicLong我们就暂时先介绍到这里,现在呢我们来看一下Atomic包。

这是JDK里面默认提供好的包,这个包里面我们可以看到我们平时用的一些基本类型,什么布尔类型啦整型啦Long型都有对应的原子相关的类,这里除了compareAndSwapInt这个函数还有一个函数compareAndSet需要大家注意一下,这个方法/函数更多的用在于AtomicBoolean这个类里面。为什么这么说呢?实际中我们经常会遇到比如我希望某件事情只执行一次,在执行这件事情之前呢它的标记可能为false,一旦执行之后我就要把它变成true。这个时候呢如果我们使用AtomicBoolean这个类里面的compareAndSet方法,分别传入false、true的时候呢,就可以保证对应我们要控制的那一段代码只执行一次,甚至可以理解为当前只有一个线程可以执行这段代码。如果我们在执行完之后再把这个变量标识为false之后,当然它可以继续执行。这样它达到的效果就是,同样的代码、同一时间只有一个线程可以执行。

4-1 线程安全性-原子性-atomic-1的更多相关文章

  1. 并发与高并发(七)-线程安全性-原子性-atomic

    一.线程安全性定义 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程 ...

  2. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  3. 线程安全性-原子性之Atomic包

    先了解什么是线程安全性:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为这个类是线程 ...

  4. 4-3 线程安全性-原子性-synchronized

    原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作.能保证同一时刻只有一个线程来对其进行操作的,除了Atomic包之外,还有锁.JDK提供锁主要分两种,synchronized是一个Java ...

  5. 4-2 线程安全性-原子性-atomic-2

    AtomicReference和AtomicLong.AtomicInteger很像,方法也基本上是一样的,然后我们通过引用Integer来做一个简单的例子. com.mmall.concurrenc ...

  6. 线程安全性-原子性之synchronized锁

    原子性提供了互斥访问:同一时刻只能有一个线程进行操作: 除了Atomic包类之外,还有锁可以实现此功能: synchronized:  java关键字,依赖于jvm实现锁功能,被此关键字所修饰的,都是 ...

  7. 并发与高并发(八)-线程安全性-原子性-synchronized

    前言 闲暇时刻,谈一下曾经在多线程教程中接触的同步锁synchronized,相当于复习一遍吧. 主要介绍 synchronized:依赖JVM Lock:依赖特殊的CPU指令,代码实现,Reetra ...

  8. Java线程安全性-原子性工具对比

    synchronized 不可中断锁,适合竞争不激烈的场景,可读性好,竞争激烈时性能下降很快 Lock 可中断锁,多样化同步,竞争激烈时能维持常态 Atomic 竞争激烈时能维持常态,比Lock性能还 ...

  9. Java并发编程 (四) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...

随机推荐

  1. Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式

    一.背景 要提升服务器的并发处理能力,通常有两大方向的思路. 1.系统架构层面.比如负载均衡.多级缓存.单元化部署等等. 2.单节点优化层面.比如修复代码级别的性能Bug.JVM参数调优.IO优化等等 ...

  2. [python] 获得所有的最长公共子序列

    两句闲话 得到两个序列的最长公共子序列(LCS)是个经典问题,使用动态规划,实现起来并不难. 一般来说,我们只是输出一个LCS.但是,老师布置的作业是输出所有的LCS. 解法 按照一般的方法,我们首先 ...

  3. SSH项目配置数据源的方法(jndi)

    1.在tomcat6.0/conf/context.xml加入以下代码 [xhtml] view plain copy     <Resource name="jdbc/oracleD ...

  4. 【Swift】- UITextField完成输入后关闭软键盘的几种方法

    总结了以下几种方式,欢迎补充  1,为空白区域绑定Touch Up Inside事件  2,重写touchesEnded方法  3,为TextField绑定Did End On Exit事件 1,点击 ...

  5. JAVA设计模式:静态代理

    一.概念代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关 ...

  6. CentOS 7 : Docker私有仓库搭建和使用

    系统环境: CentOS 7.2 192.168.0.179:docker仓库 192.168.0.60:客户端 安装并启动docker yum -y install docker systemctl ...

  7. spring mvc集成velocity使用

    目前流行的三大页面视图神器是:老牌大哥jsp.后起之秀freemarker和velocity.这里不详细比较这三者的优劣,总体来说,jsp是标配,但后面两个更严格的执行了视图与业务的分离,页面里是不允 ...

  8. Python学习笔记之os模块

    Python中的os提供了非常丰富的方法用来处理文件和目录,下面我们将详细的介绍os相关的一些方法和函数: os 路径相关的函数: 1.os.listdir(dirname):列出dirname目录下 ...

  9. php跨域问题

    http://www.cnblogs.com/xiezn/p/5651093.html

  10. git fatal: remote origin already exists. 报错解决

    在研究git的时候,随便输了个 git remote add origin xxx; 然后再真正add 远程仓库的时候,报了git fatal: remote origin already exist ...