[超级链接:Java并发学习系列-绪论]

在Java并发编程中,如果要保证代码的安全性,则必须保证代码的原子性、可见性和有序性。

在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了初步学习。

本章主要就Java中保障原子性的技术进行更加全面的学习。

1.整体回顾

  • 原子性定义:一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。
  • Java自带原子性:对基本数据类型的变量读取赋值操作是原子性操作。

2.原子性问题

由上面的章节已知,不采取任何的原子性保障措施的自增操作并不是原子性的。
下面的代码实现了一个自增器(不是原子性的)。

/*** <p>原子性示例:不是原子性</p>** @author hanchao 2018/3/10 14:58**/
static class Increment {private int count = 1;public void increment() {count++;}public int getCount() {return count;}
}

下面的代码展示了在多线程环境中,调用此自增器进行自增操作。

int type = 0;//类型
int num = 50000;//自增次数
int sleepTime = 5000;//等待计算时间
int begin;//开始的值
Increment increment;
//不进行原子性保护的大范围操作
increment = new Increment();
begin = increment.getCount();
LOGGER.info("Java中普通的自增操作不是原子性操作。");
LOGGER.info("当前运行类:" +increment.getClass().getSimpleName() +  ",count的初始值是:" + increment.getCount());
for (int i = 0; i < num; i++) {new Thread(() -> {increment.increment();}).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment.getCount());

某次运行结果:

2018-03-17 22:52:23 INFO  ConcurrentAtomicityDemo:132 - Java中普通的自增操作不是原子性操作。
2018-03-17 22:52:23 INFO  ConcurrentAtomicityDemo:133 - 当前运行类:Increment,count的初始值是:1
2018-03-17 22:52:33 INFO  ConcurrentAtomicityDemo:141 - 进过50000次自增,count应该 = 50001,实际count = 49999

通过观察结果,发现程序确实存在原子性问题。

3.原子性技术保障

在Java中提供了多种原子性保障措施,这里主要涉及三种:

  • 通过synchronized关键字定义同步代码块或者同步方法保障原子性。
  • 通过Lock接口保障原子性。
  • 通过Atomic类型保障原子性。

3.1.synchronized关键字

Increment类进行扩展:

/*** <p>原子性示例:通过synchronized保证代码块的原子性</p>** @author hanchao 2018/3/10 15:07**/
static class SynchronizedIncrement extends Increment {/*** <p>添加关键字synchronized,使之成为同步方法</p>** @author hanchao 2018/3/10 15:12**/@Overridepublic synchronized void increment() {super.count++;}
}

在多线程环境中进行SynchronizedIncrement 的自增:

//synchronized关键字能够保证原子性(代码块锁,多线程操作某一对象时,在某个代码块内只能单线程执行)
increment = new SynchronizedIncrement();
begin = increment.getCount();
LOGGER.info("可以通过synchronized关键字保障代码的原子性");
LOGGER.info("当前运行类:" +increment.getClass().getSimpleName() +  ",count的初始值是:" + increment.getCount());
for (int i = 0; i < num; i++) {new Thread(() -> {increment.increment();}).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment.getCount());

运行结果(多次):

2018-03-18 00:41:30 INFO  ConcurrentAtomicityDemo:147 - 可以通过synchronized关键字保障代码的原子性
2018-03-18 00:41:30 INFO  ConcurrentAtomicityDemo:148 - 当前运行类:SynchronizedIncrement,count的初始值是:1
2018-03-18 00:41:40 INFO  ConcurrentAtomicityDemo:156 - 进过50000次自增,count应该 = 50001,实际count = 50001

通过多次运行,发现运行结果一致,所以可以确定synchronized关键字能够保证代码的原子性

3.2.Lock接口

Increment类进行扩展:

/**
* <p>原子性示例:通过Lock接口保证指定范围代码的原子性</p>
*
* @author hanchao 2018/3/10 15:14
**/
static class LockIncrement extends Increment {//定义个读写锁:锁内运行多线程读,单线程写private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);/*** <p>运用读写所重写方法</p>** @author hanchao 2018/3/10 15:13**/@Overridepublic void increment() {//写锁 加锁readWriteLock.writeLock().lock();try {//开始写super.count++;} finally {//将解锁放在finally块中,保证必然执行,防止死锁readWriteLock.writeLock().unlock();}}
}

在多线程环境中进行LockIncrement的测试:

//通过Lock接口保证原子性操作
increment = new LockIncrement();
begin = increment.getCount();
LOGGER.info("可以通过Lock接口保证代码的原子性");
LOGGER.info("当前运行类:" +increment.getClass().getSimpleName() +  ",count的初始值是:" + increment.getCount());
for (int i = 0; i < num; i++) {new Thread(() -> {increment.increment();}).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment.getCount());

运行结果(多次):

2018-03-18 10:12:12 INFO  ConcurrentAtomicityDemo:163 - 可以通过Lock接口保证代码的原子性
2018-03-18 10:12:12 INFO  ConcurrentAtomicityDemo:164 - 当前运行类:LockIncrement,count的初始值是:1
2018-03-18 10:12:29 INFO  ConcurrentAtomicityDemo:172 - 进过50000次自增,count应该 = 50001,实际count = 50001

通过多次运行,发现运行结果一致,所以可以确定Lock接口能够保证代码的原子性

3.3.Atomic类型

Increment类进行扩展:

/**
* <p>原子性示例:通过Atomic类型保证类型的原子性</p>
*
* @author hanchao 2018/3/10 15:19
**/
static class AtomicIncrement {private AtomicInteger count = new AtomicInteger(1);/*** <p>无需其他处理,直接自增即可</p>** @author hanchao 2018/3/10 15:21**/public void increment() {count.getAndIncrement();}public AtomicInteger getCount() {return count;}
}

在多线程环境中进行AtomicIncrement的测试:

//通过Atomic变量保证变量操作的原子性
AtomicIncrement increment1 = new AtomicIncrement();
begin = increment1.getCount().get();
LOGGER.info("可以通过Atomic类型保证变量的原子性");
LOGGER.info("当前运行类:" +increment1.getClass().getSimpleName() +  ",count的初始值是:" + increment1.getCount());
for (int i = 0; i < num; i++) {new Thread(() -> {increment1.increment();}).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment1.getCount());

运行结果(多次):

2018-03-18 10:14:37 INFO  ConcurrentAtomicityDemo:178 - 可以通过Atomic类型保证变量的原子性
2018-03-18 10:14:37 INFO  ConcurrentAtomicityDemo:179 - 当前运行类:AtomicIncrement,count的初始值是:1
2018-03-18 10:14:48 INFO  ConcurrentAtomicityDemo:187 - 进过50000次自增,count应该 = 50001,实际count = 50001

通过多次运行,发现运行结果一致,所以可以确定Atomic类型能够保证代码的原子性

4.总结

经验证,以下三种措施,可以保证Java代码在运行时的原子性:

  • synchronized关键字
  • Lock接口
  • Atomic类型

并发三特性总结

特性 volatile关键字 synchronized关键字 Lock接口 Atomic变量
原子性 无法保障 可以保障 可以保障 可以保障
可见性 可以保障 可以保障 可以保障 可以保障
有序性 一定程度保障 可以保障 可以保障 无法保障

Java并发13:并发三特性-原子性定义、原子性问题与原子性保证技术相关推荐

  1. 进阶笔记——java并发编程三特性与volatile

    欢迎关注专栏:Java架构技术进阶.里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦.微信公众号:慕容千语的架构笔记.欢迎关注一起进步. 前言 前面讲过使用synchronize ...

  2. java 原子类_小学妹教你并发编程的三大特性:原子性、可见性、有序性

    在并发编程中有三个非常重要的特性:原子性.有序性,.可见性,学妹发现你对它们不是很了解,她很着急,因为理解这三个特性对于能够正确地开发高并发程序有很大的帮助,接下来的面试中也极有可能被问到,小学妹就忍 ...

  3. Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  4. blp模型 上读下写_Java高并发编程(三):Java内存模型

    1 Java内存模型的基础 在并发编程里,需要处理两个问题: 线程之间如何通信 线程之间如何同步. 通信指的是线程之间以何种机制来交换信息.在命令式编程里中,线程之间的通信机制有两种:共享内存和消息传 ...

  5. Java并发编程(三)volatile域

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或 ...

  6. Java并发编程 - 第三章 Java内存模型

    前言: Java 线程之间的通信对程序员完全透明,内存可见性问题很容易困扰 Java 程序员,本章将揭开 Java 内存模型神秘的面纱. 一.Java 内存模型的基础 1.1 并发编程模型的两个关键问 ...

  7. 【Java书笔记】:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》第2部分-自动内存管理,第3部分-虚拟机执行子系统,第5部分-高效并发

    作者:周志明 整理者GitHub:https://github.com/starjuly/UnderstandingTheJVM 第2部分-自动内存管理 第2章 Java内存区域与内存溢出异常 2.2 ...

  8. 锁的由来,并发三特性全解析

    文章目录 一.前言 二.三大源头 2.1 缓存导致可见性问题 2.1.1 理论:从单核CPU到多核CPU 2.1.2 实践:多线程可见性问题 2.2 线程切换带来的原子性问题 2.3 编译优化带来的有 ...

  9. 【Java】保证并发安全的三大特性

    一.并发编程三大特性的定义和由来 并发编程这三大特性就是为了在多个线程交替执行任务的过程中保证线程安全性. 二.为什么会出现线程不安全的现象呢? 接下来我们从这三个特性切入来介绍线程不安全的原因. 1 ...

最新文章

  1. 每周分享第8期(2019.5.25)
  2. [渝粤教育] 宁波大学 聆听中国 参考 资料
  3. 【牛客 - 185F】 假的数学游戏(斯特林公式,大数Java打表)
  4. 初中数学分几个模块_北京版初中数学:8大模块,59个必考易错知识点大集合,一定要注意!...
  5. csbte路点机器人_反恐精英csbte地图
  6. mysql多实例my.cnf_mysql多实例,my.cnf 4G conf配置安装配置
  7. c++ 编译添加dll_通达信DLL编程(三)
  8. Excel如何查找两列数据不同项
  9. 华硕服务器 bios 内存 1333 显示 800,华硕主板+宇瞻内存 服务器DIY最佳选择!
  10. 国内智能工厂建设现状以及未来发展趋势介绍
  11. 机器学习中的正则化项(L1, L2)的理解
  12. 卷积操作中的group
  13. Android存储空间总结
  14. 搜狗输入法取消软键盘
  15. Win10数字权利激活批处理版
  16. ZHO共享纸巾机为何能够引领潮流、深受大众欢迎?
  17. WPF 中 TextBlock 和 TextBox 区别
  18. 【每日收藏】资深开发者的开发工具
  19. 题目13: 单词接龙
  20. 王家耀院士 | 新型智慧城市“大脑”就是时空大数据平台

热门文章

  1. jqgrid的formatter方法
  2. 使用FormData对form表单序列化
  3. 笔记本做无线路由器图像详细教程(转)新加win7 方法:
  4. onCreate(Bundle)
  5. 面试有没有看过spring源码_怎么阅读Spring源码?
  6. 图标选择模态框antd封装
  7. python爬取12306_python爬取12306火车车次信息
  8. mock功能-了解mock的作用及使用场景
  9. 用原生js完成鼠标点击显示滑入滑出效果
  10. Vue 微信开发,微信内H5调起微信支付