【Java 并发编程】线程操作原子性问题 ( 问题业务场景分析 | 使用 synchronized 解决线程原子性问题 )
文章目录
总结
原子操作问题 : 线程中 , 对变量副本 count
进行自增操作 , 不是原子操作 , 首先 从工作内存中读取变量副本到执行引擎 ( 操作数栈 ) 中 , 然后 再 进行自增运算 , 最后 写回到线程工作内存中 , 这是
个操作 , 如果变量 在这
个操作的空档时间进行了修改 , 那么就会产生无法预知的效果 ;
总结一下 : 线程 A 的变量副本入操作数栈的时刻 , 该共享变量被线程 B 修改并且同步更新 , 此时入栈的这个变量自增是无效的 , 但是也算自增了
次 , 因此这里就丢失了
次计算机会 ;
一、原子性问题示例
开启
个线程 , 对某个线程共享 int
类型变量进行自增 , 每个线程自增
次 , 那么按照正常执行 ,
个线程执行完毕后的变量值应该是
;
代码示例 :
public class Main {
private volatile static int count = 0;
private static void increase() {
count++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i ++) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i ++) {
increase();
System.out.println(count);
}
}
}.start();
}
}
}
执行结果 : 多运行几次 , 有的时候会出现结果不是 200000
的情况 , 这就是出现问题的情景 ;
二、线程操作原子性问题分析
上述程序中 , 将变量 int count
设置成 volatile
类型的 , 只能保证其 可见性 和 有序性 , 无法保证 线程操作的 原子性 ;
在线程中对 int count = 0
进行累加操作 , 首先将变量 int count = 0
加载到线程工作内存的变量副本中 , 这里创建了
个线程 , 就会有
个线程对应的工作内存空间 , 需要将 count
变量拷贝
份到相应的线程工作内存中 ;
有这样一种极端情况 , 当某个线程 A , 将 变量副本 加载到 线程执行引擎 时 , 就是 线程栈 中的 栈帧 的的 操作数栈 中 , 此时将要开始执行相关操作 , 在线程执行引擎没有执行之前 ,
与此同时 , 线程 B 修改了 count
副本变量 , 并进行了同步 , 主内存 , 包括 线程 A 的副本变量也已经更新了最新的值 ,
当前 线程栈中的栈帧中的操作数栈 中 , 还压着一个副本变量 , 虽然 该变量已经过时 , 该 count++
操作无效 , 这样就 丢失了
次 count
变量自增的操作 , 导致 最终输出的值是
;
原子操作问题 : 线程中 , 对变量副本 count
进行自增操作 , 不是原子操作 , 首先 从工作内存中读取变量副本到执行引擎 ( 操作数栈 ) 中 , 然后 再 进行自增运算 , 最后 写回到线程工作内存中 , 这是
个操作 , 如果变量 在这
个操作的空档时间进行了修改 , 那么就会产生无法预知的效果 ;
总结一下 : 线程 A 的变量副本入操作数栈的时刻 , 该共享变量被线程 B 修改并且同步更新 , 此时入栈的这个变量自增是无效的 , 但是也算自增了
次 , 因此这里就丢失了
次计算机会 ;
三、使用 synchronized 解决线程原子性问题
使用 synchronized
修饰 increase
方法 ;
private static void increase() {
count++;
}
方法 , 相当于在方法体重添加了 synchronized
代码块 ;
private static void increase() {
synchronized (Main.class) {
count++;
}
}
一旦某个线程执行 synchronized
方法或代码块中的代码 , 则当前线程持有互斥锁 , 只能由当前线程访问 count
变量 ;
代码示例 :
public class Main {
private volatile static int count = 0;
private synchronized static void increase() {
count++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i ++) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i ++) {
increase();
System.out.println(count);
}
}
}.start();
}
}
}
执行结果 :
相关文章
- java sortedset用法_Java SortedSet headSet()用法及代码示例[通俗易懂]
- java volatile关键字的作用_Java并发编程彻底搞懂volatile关键字「建议收藏」
- Java高并发读写文件[通俗易懂]
- java helloworld源代码_Java Hello World源代码notepad++版
- java循环语句_Java中的循环语句
- java多线程面试题大全_java多线程面试题_线程并发面试题
- java官方编译器_JAVA 编译器
- java 生成xml dom4j_Java生成xml——DOM4J生成
- 【说站】java volatile变量的并发操作
- 深入讲解java多线程与高并发:线程池ThreadPool
- 大牛即将带你深入解析java虚拟机:并发设施,内存模型
- java arraydeque poll,Java ArrayDeque「建议收藏」
- java创建线程池的几种方式_Java中的线程池
- 并发多线程学习(五)Java线程的状态及主要转化方法
- 虚拟线程将会深刻影响大规模Java应用的并发机制
- 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )
- java并发编程(1):Java多线程-基本线程类-基础知识复习笔记
- JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask详解编程语言
- Java线程新特征——Java并发库详解编程语言
- MySQL与Java的数据交互之旅(mysql对应java)
- 实现高并发:Java利用Redis秒杀成功(java秒杀redis)
- Java连接Oracle实现简单快捷的数据传输(java联结oracle)
- java线程并发cyclicbarrier类使用示例
- java线程并发blockingqueue类使用示例
- Java并发编程示例(五):线程休眠与恢复
- Java并发编程示例(七):守护线程的创建和运行