Java之原子性问题的解决

2. 原子性

2.1 volatile-问题

代码分析 :

package com.itheima.myvolatile;
​
public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();
​MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}
package com.itheima.myvolatile;
​
public class Money {public static int money = 100000;
}
package com.itheima.myvolatile;
​
public class MyThread1 extends  Thread {@Overridepublic void run() {while(Money.money == 100000){
​}
​System.out.println("结婚基金已经不是十万了");}
}
​
package com.itheima.myvolatile;
​
public class MyThread2 extends Thread {@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
​Money.money = 90000;}
}

程序问题 : 女孩虽然知道结婚基金是十万,但是当基金的余额发生变化的时候,女孩无法知道最新的余额。

2.2 volatile解决

以上案例出现的问题 :

当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题

1,堆内存是唯一的,每一个线程都有自己的线程栈。

2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。

3 ,在线程中,每一次使用是从变量的副本中获取的。

Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值

代码实现 : 使用volatile关键字解决

package com.itheima.myvolatile;
​
public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();
​MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}
package com.itheima.myvolatile;
​
public class Money {public static volatile int money = 100000;
}
package com.itheima.myvolatile;
​
public class MyThread1 extends  Thread {@Overridepublic void run() {while(Money.money == 100000){
​}
​System.out.println("结婚基金已经不是十万了");}
}
​
package com.itheima.myvolatile;
​
public class MyThread2 extends Thread {@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
​Money.money = 90000;}
}
​

2.3 synchronized解决

synchronized解决 :

1 ,线程获得锁

2 ,清空变量副本

3 ,拷贝共享变量最新的值到变量副本中

4 ,执行代码

5 ,将修改后变量副本中的值赋值给共享数据

6 ,释放锁

代码实现 :

package com.itheima.myvolatile2;
​
public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();
​MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}
package com.itheima.myvolatile2;
​
public class Money {public static Object lock = new Object();public static volatile int money = 100000;
}
package com.itheima.myvolatile2;
​
public class MyThread1 extends  Thread {@Overridepublic void run() {while(true){synchronized (Money.lock){if(Money.money != 100000){System.out.println("结婚基金已经不是十万了");break;}}}}
}
package com.itheima.myvolatile2;
​
public class MyThread2 extends Thread {@Overridepublic void run() {synchronized (Money.lock) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
​Money.money = 90000;}}
}

2.4 原子性

概述 : 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

代码实现 :

package com.itheima.threadatom;
​
public class AtomDemo {public static void main(String[] args) {MyAtomThread atom = new MyAtomThread();
​for (int i = 0; i < 100; i++) {new Thread(atom).start();}}
}
class MyAtomThread implements Runnable {private volatile int count = 0; //送冰淇淋的数量
​@Overridepublic void run() {for (int i = 0; i < 100; i++) {//1,从共享数据中读取数据到本线程栈中.//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据.count++;System.out.println("已经送了" + count + "个冰淇淋");}}
}

代码总结 : count++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断

2.5 volatile关键字不能保证原子性

解决方案 : 我们可以给count++操作添加锁,那么count++操作就是临界区中的代码,临界区中的代码一次只能被一个线程去执行,所以count++就变成了原子操作。

package com.itheima.threadatom2;
​
public class AtomDemo {public static void main(String[] args) {MyAtomThread atom = new MyAtomThread();
​for (int i = 0; i < 100; i++) {new Thread(atom).start();}}
}
class MyAtomThread implements Runnable {private volatile int count = 0; //送冰淇淋的数量private Object lock = new Object();
​@Overridepublic void run() {for (int i = 0; i < 100; i++) {//1,从共享数据中读取数据到本线程栈中.//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据.synchronized (lock) {count++;System.out.println("已经送了" + count + "个冰淇淋");}}}
}

2.6 原子性_AtomicInteger

概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变

量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解

使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型

AtomicInteger: 原子更新整型

AtomicLong: 原子更新长整型

以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:

public AtomicInteger():                 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):  初始化一个指定值的原子型Integer
​
int get():                              获取值
int getAndIncrement():                   以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():                   以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):                 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value):                以原子方式设置为newValue的值,并返回旧值。

代码实现 :

package com.itheima.threadatom3;
​
import java.util.concurrent.atomic.AtomicInteger;
​
public class MyAtomIntergerDemo1 {
//    public AtomicInteger():                  初始化一个默认值为0的原子型Integer
//    public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integerpublic static void main(String[] args) {AtomicInteger ac = new AtomicInteger();System.out.println(ac);
​AtomicInteger ac2 = new AtomicInteger(10);System.out.println(ac2);}
​
}
package com.itheima.threadatom3;
​
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
​
public class MyAtomIntergerDemo2 {
//    int get():                获取值
//    int getAndIncrement():     以原子方式将当前值加1,注意,这里返回的是自增前的值。
//    int incrementAndGet():     以原子方式将当前值加1,注意,这里返回的是自增后的值。
//    int addAndGet(int data):   以原子方式将参数与对象中的值相加,并返回结果。
//    int getAndSet(int value):  以原子方式设置为newValue的值,并返回旧值。public static void main(String[] args) {
//        AtomicInteger ac1 = new AtomicInteger(10);
//        System.out.println(ac1.get());
​
//        AtomicInteger ac2 = new AtomicInteger(10);
//        int andIncrement = ac2.getAndIncrement();
//        System.out.println(andIncrement);
//        System.out.println(ac2.get());
​
//        AtomicInteger ac3 = new AtomicInteger(10);
//        int i = ac3.incrementAndGet();
//        System.out.println(i);//自增后的值
//        System.out.println(ac3.get());
​
//        AtomicInteger ac4 = new AtomicInteger(10);
//        int i = ac4.addAndGet(20);
//        System.out.println(i);
//        System.out.println(ac4.get());
​AtomicInteger ac5 = new AtomicInteger(100);int andSet = ac5.getAndSet(20);System.out.println(andSet);System.out.println(ac5.get());}
}

2.7 AtomicInteger-内存解析

AtomicInteger原理 : 自旋锁 + CAS 算法

CAS算法:

有3个操作数(内存值V, 旧的预期值A,要修改的值B)

当旧的预期值A == 内存值 此时修改成功,将V改为B

当旧的预期值A!=内存值 此时修改失败,不做任何操作

并重新获取现在的最新值(这个重新获取的动作就是自旋)

2.8 AtomicInteger-源码解析

代码实现 :

package com.itheima.threadatom4;
​
public class AtomDemo {public static void main(String[] args) {MyAtomThread atom = new MyAtomThread();
​for (int i = 0; i < 100; i++) {new Thread(atom).start();}}
}
package com.itheima.threadatom4;
​
import java.util.concurrent.atomic.AtomicInteger;
​
public class MyAtomThread implements Runnable {//private volatile int count = 0; //送冰淇淋的数量//private Object lock = new Object();AtomicInteger ac = new AtomicInteger(0);
​@Overridepublic void run() {for (int i = 0; i < 100; i++) {//1,从共享数据中读取数据到本线程栈中.//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据.//synchronized (lock) {
//                count++;
//                ac++;int count = ac.incrementAndGet();System.out.println("已经送了" + count + "个冰淇淋");// }}}
}
​

源码解析 :

//先自增,然后获取自增后的结果
public final int incrementAndGet() {//+ 1 自增后的结果//this 就表示当前的atomicInteger(值)//1    自增一次return U.getAndAddInt(this, VALUE, 1) + 1;
}
​
public final int getAndAddInt(Object o, long offset, int delta) {//v 旧值int v;//自旋的过程do {//不断的获取旧值v = getIntVolatile(o, offset);//如果这个方法的返回值为false,那么继续自旋//如果这个方法的返回值为true,那么自旋结束//o 表示的就是内存值//v 旧值//v + delta 修改后的值} while (!weakCompareAndSetInt(o, offset, v, v + delta));//作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。//                                 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。//如果修改失败,那么继续自旋。return v;
}

2.9 悲观锁和乐观锁

synchronized和CAS的区别 :

相同点:在多线程情况下,都可以保证共享数据的安全性。

不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。

如果别人修改过,那么我再次获取现在最新的值。

如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/93888.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

想要精通算法和SQL的成长之路 - 验证二叉树的前序序列化

想要精通算法和SQL的成长之路 - 验证二叉树的前序序列化 前言一. 验证二叉树的前序序列化 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 验证二叉树的前序序列化 原题链接 思路&#xff08;参考负雪明图&#xff09;&#xff1a; 首先我们看题目所给的字符串&#xff…

【Diffusion】DDPM - (2)公式推导 之 前向扩散

1、加噪过程 1、将 图像 x 0 x_0 x0​ 像素值映射到 [-1, 1] 之间 x 255 2 − 1 , w h e r e    x 为图像中的像素值 \quad \frac{x}{255} \times 2-1, \quad where \; x 为图像中的像素值 255x​2−1,wherex为图像中的像素值 \quad 2、生成一张尺寸相同的噪声图片,像…

边缘计算网关

一、项目整体框架图 二、项目整体描述 边缘计算网关项目主要实现了智能家居场景和工业物联网场景下设备的数据采集和控制。 整个项目分为三大层&#xff1a;用户接口层、网关层、设备层。 其中用户层通过QT客户端、WEB界面及阿里云提供数据展示和用户接口。 网关使用虚拟机代替…

Windows电脑上的多开器:是什么、怎么用?

多开器是一种软件&#xff0c;可以在Windows电脑上允许同时打开多个程序或游戏的工具。它通常被用于游戏&#xff0c;可以帮助用户在同一台电脑上同时登录多个游戏账号&#xff0c;或者在同一时间内同时玩多个游戏。 使用多开器的方法很简单。首先&#xff0c;需要在网上下载并…

Django之十二:模板的继承+用户列表

模板的继承 新建layout.html&#xff1a; {% load static %} <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"{% static plugins…

231003-四步MacOS-iPadOS设置无线竖屏随航SideCar

Step 0&#xff1a;MacOS到iPad无线竖屏随航显示&#xff0c;最终效果 Step 1&#xff1a; 下载 Better Display Step 2&#xff1a;在设置中新建虚拟屏幕&#xff0c;创建虚拟屏幕 Step 3&#xff1a;进行如下设置 Step 4&#xff1a;注意事项 ⚠️ 设置后的虚拟屏幕与Sideca…

nodejs+vue晓海网上订餐系统elementui

管理员功能需求 管理员登陆后&#xff0c;主要模块包括首页、个人中心、用户管理、菜单信息管理等功能。 第三章 系统分析 10 3.1需求分析 10 3.2可行性分析 10 3.2.1技术可行性&#xff1a;技术背景 10 3.2.2经济可行性 11 3.2.3操作可行性&#xff1a; 11 3.3性能分析 11 3.4…

IntelliJ IDEA配置Cplex12.6.3详细步骤

Cplex12.6.3版IntelliJ IDEA配置详细步骤 一、Cplex12.6.3版下载地址二、Cplex安装步骤三、IDEA配置CPLEX3.1 添加CPLEX安装目录的cplex.jar包到项目文件中3.2 将CPLEX的x64_win64文件夹添加到IDEA的VM options中 四、检查IDEA中Cplex是否安装成功卸载Cplex 一、Cplex12.6.3版下…

我用PYQT5做的第一个实用的上位机项目(三)

基本的程序框架&#xff1a; 因为自己不是专业的程序员&#xff0c;只是一个搞电气控制的“票友”&#xff0c;所以尽量减少手动输入 代码量&#xff0c;能在Qt Dsigner里面完成的组态就不要放在代码里面完成。 在框架的建设方面&#xff0c;尽量做到集中和整合&#xff0c;位…

科技+智慧+颜值,智慧公厕黑科技提升城市形象

现代社会的科技和智慧正以惊人的速度渗透到我们生活的各个方面&#xff0c;包括公共设施。而作为城市形象的重要组成部分之一&#xff0c;公厕也在不断创新中迎来了智慧的时代。 在传统的公厕中&#xff0c;一些问题一直困扰着我们&#xff0c;比如厕位的占用情况、空气质量的…

Git多账号管理通过ssh 公钥的方式,git,gitlab,gitee

按照目前国内访问git&#xff0c;如果不科学上网&#xff0c;我们很大可能访问会超时。基于这个&#xff0c;所以我现在的git 配置已经增加到了3个了 一个公司gitlab&#xff0c;一个git&#xff0c;一个gitee. 以下基于这个环境&#xff0c;我们来说明下如何创建配置ssh公钥。…

UE中制作棋盘格材质效果

在UE中通过这个小技巧制作棋盘格材质效果&#xff0c;可以快速预览UV拉伸情况&#xff0c;方便调试导入的模型。 1.操作步骤 1.1 首先新建材质&#xff0c;Shading Model&#xff08;着色模式&#xff09;设置为Unlit&#xff08;无光照&#xff09;&#xff1a; 1.2 我们…

前端相关题目随笔

Vh虽然获取到了视口高度&#xff0c;但是vh会随着屏幕的、大小变化&#xff0c;所以当减去一个数字之后&#xff0c;就会显示错误。 生成id 如果没有设置id&#xff0c;则可以通过new Date.getTime()获取一个时间&#xff0c;作为一个单独的id&#xff0c;也可以通过下载uuid生…

JavaScript系列从入门到精通系列第十二篇:JavaScript中对象的简介和对象的基本操作以及JavaScript中的属性值和属性名

文章目录 前言 一&#xff1a;对象分类 1&#xff1a;内建对象 2&#xff1a;宿主对象 3&#xff1a;自建对象 二&#xff1a;对象的基本操作 1&#xff1a;创建对象 2&#xff1a;向对象中添加属性 3&#xff1a;读取对象中的属性 4&#xff1a;修改对象中的属性 三…

机器学习7:逻辑回归

一、说明 逻辑回归模型是处理分类问题的最常见机器学习模型之一。二项式逻辑回归只是逻辑回归模型的一种类型。它指的是两个变量的分类&#xff0c;其中概率用于确定二元结果&#xff0c;因此“二项式”中的“bi”。结果为真或假 — 0 或 1。 二项式逻辑回归的一个例子是预测人…

【PickerView案例12-info_plist-PCH文件介绍 Objective-C语言】

一、给大家介绍一下我们项目的一些文件: 1.这个呢,是项目的基础文件: 一些类啊: 一些图片啊: 还有加载图片, 最主要,就是这个东西:info.plist:文件 info.plist: 2.那,需要大家了解一点,关于它的历史啊: 我们现在用的时候,都是从xcode6.4开始的, 或者说,直…

Python 数据分析与挖掘(一)

Python 数据分析与挖掘&#xff08;数据探索&#xff09; 数据探索 1.1 需要掌握的工具&#xff08;库&#xff09; 1.1.1 Nump库 Numpy 提供多维数组对象和各种派生对象&#xff08;类矩阵&#xff09;&#xff0c;利用应用程序接口可以实现大量且繁琐的数据运算。可以构建…

Linux 5种网络模型

[参考]&#xff1a;《黑马程序员Redis》https://www.bilibili.com/video/BV1cr4y1671t/?p166&share_sourcecopy_web&vd_source9e65300ccca322aeb367bb1eb677b0fc [参考]&#xff1a;《操作系统》 [参考]&#xff1a;《UNIX网络编程》 为了避免用户应用导致冲突甚至内…

基于SSM的奶茶店管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

基于Matlab实现标准规划问题求解(源码+数据)

Matlab是一种功能强大的数值计算和数据分析软件&#xff0c;它在解决各种数学问题中都具有很高的效率和灵活性。其中&#xff0c;Matlab也可以用于求解标准规划问题&#xff0c;即线性规划问题。 标准规划问题是一种常见的优化问题&#xff0c;其目标是在给定的约束条件下&…