在了解JDK提供的线程原子性操作工具类之前,我们应该先知道什么是原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰。接下来我们就学习JDK中线程的原子性操作。

一、CAS原理

  说道原子性,不得不提的就是CAS原理:

  使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。而CAS操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因此,线程就不会出现阻塞停顿的状态。那么,如果出现冲突了怎么办?无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。

  CAS既然和锁有关,那么它和Synchronized有什么区别呢:

  元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的间线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。

二、原子更新基本类型

  在我们实际开发中,线程原子性问题是比较常见的地方,最简单的例子:i ++;i ++是我们开发中一定会用到的一段代码,但是你知道吗,i ++在多线程环境中是不安全的,我们接下来通过一个例子来说明为什么i ++不安全:

  

public class Demo2 implements Runnable {

    //给线程方法传递一个参数
static int count=0; /**
*线程任务,将count的值遍历到10000再输出
*/
public void run() {
System.out.println("当前线程获取count的值为:"+count);
for (int i = 0; i < 10000; i++) {
count++;
}
} public static void main(String[] args) throws InterruptedException {
//创建多线程环境,这里创建了10个线程
Thread[] thread = new Thread[10];
//未创建的多线程中添加线程任务
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Demo2());
}
//启动每个线程任务
for (int i = 0; i < 10; i++) {
thread[i].start();
}
//join方法的作用是阻塞主线程,防止还没有计算完成,就开始输出count的值了
for (int i = 0; i < 10; i++) {
thread[i].join();
}
System.out.println("count计算的结果是:"+count);
}
}

  运行的结果是:

  

  这里的 count++就是我们说的i ++;可以很明确的看到,count的值除了重复获取外, 执行count++操作时,也是有问题的,这个问题的原因是:count在执行++操作时,因为是多线程环境,假设count的值在第一个线程获取为0了但是还没有完成++操作,就有第二个线程抢到了CPU资源,此时count的值还是0,所以就出现了这个数据重复获取,导致count++操作不准确,因此count++是线程不安全的。

  面对这样的问题,JDK为我们提供了原子基本类型来解决,一个有三个:

    AtomicBoolean:原子更新布尔类型

     AtomicInteger:原子更新整型

     AtomicLong:原子更新长整型

  这个三个类中的方法都一致,我们主要使用的是:

  get():直接返回值

  getAndIncrement():以原子方式将当前值加1,相当于线程安全的i++操作

  incrementAndGet():以原子方式将当前值加1,相当于线程安全的++i操作

  其他的方法可以参考AtomicInteger的API文档,因此我们改造上面的场景:

  

public class Demo2 implements Runnable {

    //给线程方法传递一个参数
static AtomicInteger count=new AtomicInteger(0); /**
*线程任务,将count的值遍历到10000再输出
*/
public void run() {
System.out.println("当前线程获取count的值为:"+count);
for (int i = 0; i < 10000; i++) {
count.incrementAndGet();
}
} public static void main(String[] args) throws InterruptedException {
//创建多线程环境,这里创建了10个线程
Thread[] thread = new Thread[10];
//未创建的多线程中添加线程任务
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Demo2());
}
//启动每个线程任务
for (int i = 0; i < 10; i++) {
thread[i].start();
}
//join方法的作用是阻塞主线程,防止还没有计算完成,就开始输出count的值了
for (int i = 0; i < 10; i++) {
thread[i].join();
}
System.out.println("count计算的结果是:"+count);
}
}

运行结果:

可以看到计算的结果是准确的,证明我们的count ++操作是线程安全的

三、原子更新数组类型

  atomic包中更新数组类型的方式与更新基本类型类似,一共提供了三种方式:

    AtomicIntegerArray:原子更新整型数组中的元素;

    AtomicLongArray:原子更新长整型数组中的元素;

    AtomicReferenceArray:原子更新引用类型数组中的元素

    三个类的用法都很相似,我们以AtomicIntegerArray类来作为介绍,一共常用的有3个方法:

  addAndGet(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;

  getAndIncrement(int i):以原子更新的方式将数组中索引为i的元素自增加1;

  compareAndSet(int i, int expect, int update):将数组中索引为i的位置的元素进行更新(注意这个是非原子性操作)

  我们就用这个几个方法写一个实例:

  

public class Demo2 implements  Runnable{

    //给线程方法传递一个参数
static int[] arr = new int[]{0, 0}; //将创建的数组传递给AtomicIntegerArray,进行原子性操作
static AtomicIntegerArray count=new AtomicIntegerArray(arr); /**
*线程任务,将count第一个元素的值赋值让其等于i的值,将第二个元素的值自增
*/
public void run() {
for (int i = 0; i < 10000; i++) {
//以原子更新的方式将数组中索引为i的元素与输入值相加
count.getAndSet(0, i);
//以原子更新的方式将数组中索引为i的元素自增加1
count.getAndIncrement(1);
}
} public static void main(String[] args) throws InterruptedException {
//创建多线程环境,这里创建了10个线程
Thread[] thread = new Thread[10];
//未创建的多线程中添加线程任务
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Demo2());
}
//启动每个线程任务
for (int i = 0; i < 10; i++) {
thread[i].start();
}
//join方法的作用是阻塞主线程,防止还没有计算完成,就开始输出count的值了
for (int i = 0; i < 10; i++) {
thread[i].join();
}
System.out.println("计算完成后count数组第一个元素的值为:"+count.get(0));
System.out.println("计算完成后count数组第二个元素的值为:"+count.get(1));
} }

运行的结果:

可以看到,我们在线程任务中进行的自增和赋值操作都是原子性的

四、原子更新类 

  一共有三种方式

  AtomicReference:原子更新引用类型

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

  AtomicMarkableReference:原子更新带有标记位的引用类型

  主要使用的是get和set方法,我们来看一个实例:

public class Demo2 implements  Runnable{

    //创建一个原子更新引用,引用一个Bean对象
static AtomicReference<Bean> count=new AtomicReference(); /**
*线程任务,对bean对象的字段赋值
*/
public void run() {
for (int i = 0; i < 10000; i++) {
//以原子更新的方式将数组中索引为i的元素与输入值相加
Bean bean=new Bean();
bean.setName("一共有"+i+"次执行");
bean.setNum(i);
count.set(bean);
}
} public static void main(String[] args) throws InterruptedException {
//创建多线程环境,这里创建了10个线程
Thread[] thread = new Thread[10];
//未创建的多线程中添加线程任务
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Demo2());
}
//启动每个线程任务
for (int i = 0; i < 10; i++) {
thread[i].start();
}
//join方法的作用是阻塞主线程,防止还没有计算完成,就开始输出count的值了
for (int i = 0; i < 10; i++) {
thread[i].join();
}
System.out.println(count.get().getName());
System.out.println("赋值的结果是"+count.get().num);
} }

其中bean对象的代码是:

  

public class Bean {

    String name;

    int num;

    public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getNum() {
return num;
} public void setNum(int num) {
this.num = num;
} }

运行的结果

五、原子更新字段

  AtomicReference:原子更新引用类型;

  AtomicReferenceFieldUpdater:原子更新引用类型里的字段;

  AtomicMarkableReference:原子更新带有标记位的引用类型;

public class Demo2 implements  Runnable{

    static Bean bean=new Bean();
//创建一个原子更新字段对象,更新bean中的num属性
static AtomicIntegerFieldUpdater<Bean> count=AtomicIntegerFieldUpdater.newUpdater(Bean.class,"num"); /**
*线程任务,对bean对象的字段赋值
*/
public void run() {
for (int i = 0; i < 10000; i++) {
//以原子更新的方式将数组中索引为i的元素与输入值相加 bean.setNum(i);
count.getAndIncrement(bean);
}
} public static void main(String[] args) throws InterruptedException {
//创建多线程环境,这里创建了10个线程
Thread[] thread = new Thread[10];
//未创建的多线程中添加线程任务
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Demo2());
}
//启动每个线程任务
for (int i = 0; i < 10; i++) {
thread[i].start();
}
//join方法的作用是阻塞主线程,防止还没有计算完成,就开始输出count的值了
for (int i = 0; i < 10; i++) {
thread[i].join();
}
System.out.println("赋值的结果是"+count.get(bean));
} }

  

java并发学习--第四章 JDK提供的线程原子性操作工具类的更多相关文章

  1. java并发学习--第七章 JDK提供的线程工具类

    一.ThreadLocal ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线 ...

  2. Java并发程序设计(四)JDK并发包之同步控制

    JDK并发包之同步控制 一.重入锁 重入锁使用java.util.concurrent.locks.ReentrantLock来实现.示例代码如下: public class TryReentrant ...

  3. java并发学习第五章--线程中的锁

    一.公平锁与非公平锁 线程所谓的公平,就是指的是线程是否按照锁的申请顺序来获取锁,如果是遵守顺序来获取,这就是个公平锁,反之为非公平锁. 非公平锁的优点在于吞吐量大,但是由于其不是遵循申请锁的顺序来获 ...

  4. java并发学习--第六章 线程之间的通信

    一.等待通知机制wait()与notify() 在线程中除了线程同步机制外,还有一个最重要的机制就是线程之间的协调任务.比如说最常见的生产者与消费者模式,很明显如果要实现这个模式,我们需要创建两个线程 ...

  5. java并发学习--第三章 线程安全问题

    线程的安全问题一直是我们在开发过程中重要关注的地方,出现线程安全问题的必须满足两个条件:存在着两个或者两个以上的线程:多个线程共享着共同的一个资源, 而且操作资源的代码有多句.接下来我们来根据JDK自 ...

  6. “全栈2019”Java多线程第二十四章:等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. 《Java程序设计》第四章-认识对象

    20145221<Java程序设计>第四章-认识对象 总结 教材学习内容总结 类与对象 定义:对象是Java语言中重要的组成部分,之前学过的C语言是面向过程的,而Java主要是面向对象的. ...

  8. “全栈2019”Java多线程第十四章:线程与堆栈详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. “全栈2019”Java异常第十四章:将异常输出到文本文件中

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

随机推荐

  1. 国内npm镜像使用方法

    npm全称Node Package Manager,是node.js的模块依赖管理工具.由于npm的源在国外,所以国内用户使用起来各种不方便.下面整理出了一部分国内优秀的npm镜像资源,国内用户可以选 ...

  2. HDU 6592 (LIS+输出字典序最大最小)

    题意:给你一个序列,让你找长度最长的字典序最小和最大的单峰序列,单峰序列就是满足先增后降的序列. 思路:先正着求一遍LIS,再反着求一遍LIS,然后用单调栈来模拟. 求字典序最小的话,首先找到第一个顶 ...

  3. 麦子lavarel---10、一些第三方应用注意

    麦子lavarel---10.一些第三方应用注意 一.总结 一句话总结: 其实把重要的几个功能弄一个就好了,邮箱验证,手机号验证,支付验证,都是调用第三方接口,也很简单 1.关于页面和服务端校验的看法 ...

  4. 【转】GLSL资料收集

    https://blog.csdn.net/u013467442/article/details/44457869 其中入门资料相当好:https://blog.csdn.net/racehorse/ ...

  5. ORACLE动态监听

    动态监听的原理 pmon在数据库启动到mount或open时,动态从参数文件中读取service_names值.service_names可以为多值(可以有64个,其中包括两个系统的).  servi ...

  6. javascript处理json字符串

    字符串转JSON格式 var obj = JSON.parse(json字符串); 判断字段值是否存在,返回true或false obj.hasOwnProperty("error" ...

  7. 用Vue来实现购物车功能(二)

    这个小demo具有添加商品进购物车 .增加购物车内商品的数量.减少购物车内商品的数量.计算一类商品的总价.以及计算所有商品的总价 首先看目录结构 因为我们的Tab.vue  Car.vue 以及Car ...

  8. Powershell 邮件发送

    目录 目录 前言 Send-MailMessage NETMail 使用OutLook发送邮件 前言 最近领导想在winServer2012上搞个自动发送邮件的计划任务,下面有几种发送邮件的方式. 如 ...

  9. 《图解设计模式》读书笔记9-1 Flyweight模式

    目录 模式简介 示例代码 代码功能与实现思路 类图 代码 结果图示分析 模式角色和类图 角色 类图 拓展思路 对多个地方产生影响 什么要共享,什么不要共享 垃圾回收 模式简介 Flyweight是轻量 ...

  10. Selfishness is not living as one wishes to live. it is asking others to live as wishes to live.

    regin: n. 统治; 任期 lap:n. 大腿部. procession: n. 行列,游行 lessen: n. 减少 wade: v. 跋涉 patriotic: adj. 爱国的 Medi ...