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

时间:2023-02-14 21:55:56

   在了解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);
}
}

  运行的结果是:

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

  这里的 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);
}
}

运行结果:

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

可以看到计算的结果是准确的,证明我们的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));
} }

运行的结果:

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

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

四、原子更新类 

  一共有三种方式

  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;
} }

运行的结果

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

五、原子更新字段

  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. &OpenCurlyDoubleQuote;全栈2019”Java多线程第二十四章:等待唤醒机制详解

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

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

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

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

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

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

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

随机推荐

  1. SQLServer 日期函数大全

    一.统计语句 1.--统计当前[>当天00点以后的数据] ) ) ORDER BY dateandtime DESC 2.--统计本周 3.--统计本月 4.统计当前 SELECT * FROM ...

  2. Hadoop2&period;4代码的坑

    org.apache.hadoop.hdfs.server.datanode.BlockPoolManager 和org.apache.hadoop.hdfs.server.datanode.Data ...

  3. UVa 12563 &lpar;01背包&rpar; Jin Ge Jin Qu hao

    如此水的01背包,居然让我WA了七次. 开始理解错题意了,弄反了主次关系.总曲目最多是大前提,其次才是歌曲总时间最长. 题意: 在KTV房间里还剩t秒的时间,可以从n首喜爱的歌里面选出若干首(每首歌只 ...

  4. 关于UT的一些总结

    本文是个人对于UT的一些想法和总结,参考时建议请查阅官方资料. 转载请注明出处:http://www.cnblogs.com/sizzle/p/4476392.html 测试思想 编写UT测试代码,通 ...

  5. eclipse &plus; maven &plus; jboss 遇到ClassNotFoundException

    在使用eclipse + maven + jboss开发过程中,碰到ClassNotFoundException, 原因应该是deployed包中未包含maven的依赖jar. 可以通过如下方法把依赖 ...

  6. 捕鱼达人代码例子下载地址 mac版

    捕鱼达人代码例子下载地址  mac版: http://pan.baidu.com/share/link?shareid=1431898404&uk=3189484501

  7. 统计nginx单个IP访问日志并获取IP来源

    #!/usr/bin/env python #coding=utf-8 import requests from urllib2 import urlopen # import lxml.html f ...

  8. PostgreSQL For Windows 全功能精简版

    预览 精简部分 保留全部 PostgreSQL 相关功能 删除自带的 pgadmin 4 删除文档 删除开发用头文件 删除开发用静态连接库 删除 Stack Build 工具 写了一个管理数据库用的批 ...

  9. That girl

    音标 词汇 Purple Glasses Black Hat Brown Bag Clothes Blue Jacket Pink Handbag Sock White Skirt Shoe 1, s ...

  10. 何谓domReady

    我的博客已经写过好几篇如何实现domReady的文章,最近做培训,面向新手们,需要彻彻底底向他们说明这个东西,于是就有了这篇文章. 我们经常看人们用 document.getElementById(& ...