Java之原子性-乐观锁与悲观锁
1、volatile-问题
1.1、代码分析 :
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;}
}
1.2、运行的结果
通过上述的的代码我们会看到,虽然我们在Thread2线程中将Money设为了9000元,但是在Thread1线程中它拿到的money仍然是10000元,所以它在控制台中没有任何的输出。那么为什么会出现上述的情况呢?
大多数人可能会有疑问,线程不是共享数据吗?怎么在这里就不起作用了?请看下图:
上图中表示的是在Java内存中的示意图,当程序运行起来后,如果女孩线程先抢到了CUP的执行权后,它会执行以下操作:获取共享数据、将共享数据在线程栈中临时存储,这个临时存储的空间我们称为变量副本,以后线程中使用的数据,就是从变量副本中获取的。
当男孩线程也获取到了CPU的执行权后,它同样也做了女孩线程同样的事情,将共享数据十万在线程中临时存储,这个时候男孩线程会修该数据,它首先是将九万赋值给变量副本,然后再把变量副本中的九万赋值给共享数据,这时,共享数据中的值才变成了九万。但是此时女孩线程中的变量副本的值仍是十万,它会去看共享数据中的值是否为十万,但是她什么时候去看,我们不知道,所以就造成了上述情况。
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;}
}
3、synchronized解决
synchronized解决 :
- 线程获得锁
- 清空变量副本
- 拷贝共享变量最新的值到变量副本中
- 执行代码
- 将修改后变量副本中的值赋值给共享数据
- 释放锁
代码实现 :
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;}}
}
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++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断
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 + "个冰淇淋");}}}
}
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());}
}
7、AtomicInteger-内存解析
AtomicInteger原理 : 自旋锁 + CAS 算法
CAS算法:
- 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
- 当旧的预期值A == 内存值 此时修改成功,将V改为B
- 当旧的预期值A!=内存值 此时修改失败,不做任何操作
- 并重新获取现在的最新值(这个重新获取的动作就是自旋)
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;
}
9、悲观锁和乐观锁
synchronized和CAS的区别 :
相同点:在多线程情况下,都可以保证共享数据的安全性。
不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。
如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)
Java之原子性-乐观锁与悲观锁相关推荐
- **Java有哪些悲观锁的实现_面试4连问:乐观锁与悲观锁的概念、实现方式、场景、优缺点?...
推荐阅读: 数据库面试4连问:分库分表,中间件,优缺点,如何拆分? 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够? 消息队列面试,你能顶得住面试官这波10大连环炮的攻势吗? 01 乐观锁 ...
- 详解各种锁:CAS、共享锁、排它锁、互斥锁、悲观锁、乐观锁、行级锁、表级锁、页级锁、死锁、JAVA对CAS的支持、ABA问题、AQS原理
共享锁(S锁) 又称为读锁,可以查看但无法修改和删除的一种数据锁.如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁.获准共享锁的事务只能读数据,不能修改数据. 共享锁下其它用 ...
- Java并发篇_乐观锁与悲观锁
乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展. 一.引入概念 1.悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次 ...
- mysql原子性和乐观锁_乐观锁 VS 悲观锁
1.乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度.在Java和数据库中都有此概念对应的实际应用. 1.1 概念悲观锁:对于同一个数据的并发操作,悲观锁认为自己在 ...
- Java多线程学习总结(5)——乐观锁和悲观锁的基本概念、实现方式(含实例)、适用场景及常见面试题
分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!点击浏览教程 一.基本概念 乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题. 乐观锁 ...
- Java并发 乐观锁和悲观锁 乐观锁的一种实现方式CAS
为什么80%的码农都做不了架构师?>>> 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人 ...
- 一篇文章带你弄懂乐观锁与悲观锁,CAS原子性,synchronized底层原理
文中加入了个人理解,如有不准确的地方欢迎提出,笔者会及时的进行改正. 乐观锁与悲观锁 乐观锁: 假设数据不会发生冲突,只有在进行数据更新的才会对数据进行检查,如果冲突则更新失败并返回错误信息 悲观锁: ...
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
Java并发问题–乐观锁与悲观锁以及乐观锁的一种实现方式-CAS </h1><div class="clear"></div><div c ...
- Java 乐观锁和悲观锁
文章目录 Java 乐观锁和悲观锁 1.悲观锁 2.乐观锁 2.1 CAS 2.2 模拟CAS算法 2.3 JUC 2.4 CAS中的ABA问题 2.5 使用CAS会引发的问题 Java 乐观锁和悲观 ...
- java的乐观锁和悲观锁
参考: https://www.cnblogs.com/jyroy/p/11365935.html https://www.jianshu.com/p/ae25eb3cfb5d 乐观锁和悲观锁 乐观锁 ...
最新文章
- Science:比较基因组揭示银边鱼应对捕鱼行为的表型进化机制
- SQL语句小tips(持续更新)
- python制作缩略图
- python一行代码打印Love心形
- 【HDU - 1968】【UVA - 12096】The SetStack Computer (模拟,集合求交集并集操作,STL实现)
- 召唤AI大神与病毒作战!Kaggle发起CORD-19数据集文本挖掘竞赛
- TortoiseGit使用
- java语言的科学与艺术-编程练习10.4
- java jni编译_从源码编译Android系统的Java类库和JNI动态库的方法
- FT计算机系统,芯片CP/FT测试的基本概念理解
- Java阻塞队列的实现
- flask前端优化:css/js/html压缩
- 随机森林-科比生涯数据集分析与预测
- ABP开发框架前后端开发系列---(1)框架的总体介绍
- 用Photoshop进行icon的制作或将其它格式图片转成icon
- Stata:中介效应理论及sgmediation命令做sobel检验
- 最新爱云发卡系统源码公益版
- 【Unity3D】AR游戏制作 - Sikuto's Farm
- .o0博客导读0o. 1/23/2016最后更新
- 大顶堆及小顶堆求最大或最小K个数
热门文章
- 淘宝客如何通过闲鱼引流?淘宝客、闲鱼营销推广方式有哪些?
- 区块链技术应用落地区块链溯源应用
- Qt边框border概述
- win10搜索服务器文件慢,如何解决win10搜索速度很慢的情况呢?|win10加快系统搜索速度的方法...
- win10 计算机 搜索文件夹,win10如何搜索文件或文件夹_win10怎么全盘搜索文件-win7之家...
- 人类一败涂地做图教程_人类一败涂地-怎么制作地图-地图制作教程详细入门级...
- UltraEdit最新版v27软件下载 程序员必用高级文本编辑器
- 脂肪秤方案中测脂模块如何实现BIA测量法功能?
- 抖音做我女朋友的 vbs 脚本
- 计算机登陆后如何防止自动注销,登录win10系统后自动注销的解决方法