(请观看本人博文 —— 《详解 多线程》)



在讲解本篇博文的知识点之前,本人先来给出一个例子:

package edu.youzg.about_synchronized.core;

public class Test {

    public static void main(String[] args) {
TestRunnable myRunnable = new TestRunnable();
new Thread(myRunnable).start();
while (true){
if (TestRunnable.getFlag()) {
System.out.println("进来了");
break;
}
}
}
} class TestRunnable implements Runnable{
static boolean flag = false;
public static boolean getFlag() {
return flag;
} @Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag=true;
System.out.println("flag的值是" + getFlag());
}
}

那么,现在本人来展示下运行结果:



可以看到,在上图中出现了这样的错误:

程序一直在运行!

那么,我们不是已经在run()中把flag设置为true了吗,我们让线程跑起来之后,再调用getFlag()方法,理应返回的是true啊。

但是,运行结果明显表明:返回的是false。

这是为什么呢?

对于上述问题,我们有一个专门的称呼 —— 内存可见性问题

那么,现在,本人就来讲解下 内存可见性问题:

内存可见性问题

首先,本人来讲解下内存模型:

内存模型:

Java内存模型规定了 :

所有的 变量 都存储在 主内存中;

而 每条线程中还有自己的工作内存。

线程的工作内存中保存了被该线程所使用到的变量

(这些变量是从主内存中拷贝而来)。

线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。

不同线程之间也无法直接访问对方工作内存中的变量,

线程间变量值的传递均需要通过主内存来完成。

而对于上面出现的问题,本人来通过几张图来展示下原因:

首先,每个线程都会有一个独立的工作线程。

假设子线程先读入了主存中的flag值,并将工作内存中的flag改为了true:



这时,主线程竞争到了CPU,读入了主存中的flag的值:



这时,不论再竞争多少轮,只要子线程竞争到CPU,就会把工作线程中的flag的值返给主存:



但是,由于这时的主线程在循环中,循环的运行速度非常快,就导致主线程无暇去主存中读入新的flag的值,进而导致循环一直在进行,并且主线程的工作线程中的flag一直都是false,所以就一直循环,导致程序无法结束!

其实,对于以上问题,我们用之前博文所讲的“synchronized锁”的知识,也完全可以去解决这个问题:

本人现在来展示下加锁解决的代码:

package edu.youzg.about_synchronized.core;

public class Test {

    public static void main(String[] args) {
TestRunnable myRunnable = new TestRunnable();
new Thread(myRunnable).start();
while (true){
synchronized (myRunnable) {
if (TestRunnable.getFlag()) {
System.out.println("进来了");
break;
}
}
}
}
} class TestRunnable implements Runnable{
static boolean flag = false;
public static boolean getFlag() {
return flag;
} @Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag=true;
System.out.println("flag的值是" + getFlag());
}
}

那么,本人再来展示下运行结果:



但是,本人之前说过,加锁有一个极大的缺点 —— 会降低运行效率

本人上图展示的仅是一个子线程和一个主线程.

那么,若是我们将来所做的app,同时去处理成百上千的线程呢?

产生的结果想想就觉得可怕... ...

那么,有没有办法能够 既解决内存可见性问题,又不会降低运行效率呢?

针对这种需求,Java有一种自己的解决办法 —— volatile关键字:


volatile关键字

概述:

当多个线程进行操作共享数据时,可以保证内存中的数据可见

相较于 synchronized 是一种较为轻量级的同步策略

也可以将volatile看作是一个轻量级锁

那么,对于上述解释,相信有的同学已经搞不懂了

那么,本人再来讲解 volatile关键字和 synchronized锁 的区别:

区别:

volatile 对于多线程,不是一种互斥关系

volatile 不能保证变量状态的“原子性操作”

那么,现在,本人来展示下用 volatile关键字 来解决上述问题:

package edu.youzg.about_synchronized.core;

public class Test {

    public static void main(String[] args) {
TestRunnable myRunnable = new TestRunnable();
new Thread(myRunnable).start();
while (true){
if (myRunnable.getFlag()) {
System.out.println("进来了");
break;
}
} }
} class TestRunnable implements Runnable{
volatile boolean flag=false;
public boolean getFlag() {
return flag;
} @Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag=true;
System.out.println("flag的值是"+getFlag());
}
}

本人再来展示下运行结果:


现在,本人再来给出一个有问题的代码:

package edu.youzg.about_synchronized.core;

import java.util.concurrent.atomic.AtomicInteger;

public class Test {

    public static void main(String[] args) {
IRunnable iRunnable = new IRunnable();
for (int i = 0; i < 10; i++) {
new Thread(iRunnable).start();
}
}
} class IRunnable implements Runnable{
static int i = 0; @Override
public void run() {
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i++);
}
}
}

由于本人设置的是死循环,所以展示运行中的一个片段:



可以看到,本人设置的static(静态)的变量的值,明明每次执行的是i++操作,却有相同的输出结果,这是为什么呢?

相信看过本人之前博文的同学已经知道了:

由于i++不是原子型操作,所以就会出现一个线程对i的 读、改、写三步操作还未完成,就被另一个线程占据了CPU执行权,就导致了这种现象的出现。

本人在之前的博文中,对于这种问题的解决,用的是 “synchronized 锁”的知识点:

但是, “synchronized 锁”会使得程序的效率大大降低,

所以,针对这种 原子性问题,Java也同样有一种解决方案 —— CAS算法:


CAS算法:

概述:

CAS (Compare-And-Swap,即:比较并交换) 是一种硬件对并发的支持,

针对多处理器操作而设计的处理器中的一种特殊指令,

用于管理对共享数据的并发访问

实现原理:

CAS是一种无锁算法

CAS有3个操作数:内存值V,旧的预期值A,要修改的新值B。

当且仅当预期值A和内存值V相同时,将内存值V修改为B,

否则什么都不做。


CAS算法的伪代码可以表示为:

do {

备份旧数据;

基于旧数据构造新数据;

} while (!CAS( 内存地址,备份的旧数据,新数据 ))

本人再来用几张图片来展示下这个算法的基本原理:

首先,假设线程1争取到了CPU:



然后,假设线程2 争取到了CPU:



这时,又轮到了线程1争取到了CPU,线程1发现A1和V值相同,于是将B1的值传回给主存,将V的值改为value2:



假设现在,线程2 又抢到了CPU的运行权,它发现A2和V 不一样,于是放弃了写入的操作,并将之前的A2、B2全部清空:

以上,就是CAS算法的基本流程,至于怎么实现,本人拿它的实现类来展示下相关代码:




现在,本人再来说明一点:

jdk5增加了并发包java.util.concurrent.*,

其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。

JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

缺点:

  1. 对资源的开销大
  2. 只能保证 变量的原子性 而不能保证 代码块的原子性

运用CAS算法实现的 原子操作的常用类:

AtomicBoolean 、 AtomicInteger 、 AtomicLong 、 AtomicReference

AtomicIntegerArray 、 AtomicLongArray

AtomicMarkableReference

AtomicReferenceArray


那么,本人就拿 AtomicInteger来解决下上面的问题:

package edu.youzg.about_synchronized.core;

import java.util.concurrent.atomic.AtomicInteger;

public class Test {

    public static void main(String[] args) {
//CAS 算法:比较并交换
//是一种硬件支持
IRunnable iRunnable = new IRunnable();
for (int i = 0; i < 10; i++) {
new Thread(iRunnable).start();
}
}
} class IRunnable implements Runnable{
// int i=0;
//把普通变量换成原子变量,
AtomicInteger num=new AtomicInteger(1);
@Override
public void run() {
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num.getAndIncrement());
}
}
}

本人还是来截取一段运行结果来展示:



可以看到,并没有出现重复值的问题!


那么,说到 CAS算法,本人就不得不为同学们来扩展下知识点了:

扩展 —— 乐观锁 与 悲观锁:

悲观锁:

概述:

总是假设最坏的情况。

每次去拿数据的时候都认为别人会修改,

所以每次在拿数据的时候都会上锁,

这样别人想拿这个数据就会 阻塞 直到别人拿到锁。

举例:

Java里面的同步原语 synchronized 关键字的实现就是悲观锁

优点:

  • 充分保证线程安全性

缺点:

  • 降低运行效率

乐观锁:

概述:

总是假设最好的情况。

每次去拿数据的时候都认为别人不会修改,

所以不会上锁,

但是在更新时会 判断一下在此期间别人有没有去更新这个数据 (可以使用版本号等机制)

举例:

乐观锁适用于 多读 的应用类型,这样可以提高吞吐量,

像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。

在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 —— CAS算法 实现的

优点:

  • 非阻塞,效率高

缺点:

  • 会造成大量开销

(本人 《详解 多线程》 博文 链接:https:////www.cnblogs.com/codderYouzg/p/12418935.html)

详解 volatile关键字 与 CAS算法的更多相关文章

  1. 详解volatile关键字和原子引用

    本篇看一下Volatile关键字和原子引用. 上图就是JUC包结构,总共分成三块 (1)java.util.concurrent:并发包基础类,包括阻塞队列,线程池相关类,线程安全Map等. (2)j ...

  2. 详解volatile 关键字与内存可见性

    先来看一个例子: public class VolatileTest {            public static void main(String[] args) {           T ...

  3. Java并发指南3:并发三大问题与volatile关键字,CAS操作

    本文转载自互联网,侵删   序言 先来看如下这个简单的Java类,该类中并没有使用任何的同步. 01 final class SetCheck { 02 private int  a = 0; 03 ...

  4. 详解十大经典数据挖掘算法之——Apriori

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是机器学习专题的第19篇文章,我们来看经典的Apriori算法. Apriori算法号称是十大数据挖掘算法之一,在大数据时代威风无两,哪 ...

  5. 详解十大经典机器学习算法——EM算法

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是机器学习专题的第14篇文章,我们来聊聊大名鼎鼎的EM算法. EM算法的英文全称是Expectation-maximization al ...

  6. Java(2)详解注释&关键字&常量&变量&标识符

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201497.html 博客主页:https://www.cnblogs.com/testero ...

  7. Java之static静态关键字详解|final关键字详解

    前言 在Java语言中,static表示"静态"的意思,使用场景可以用来修饰成员变量和成员方法,当然也可以是静态代码块.static的主要作用在于创建独立于具体对象的域变量或者方法 ...

  8. Lua5.4源码剖析:二. 详解String数据结构及操作算法

    概述 lua字符串通过操作算法和内存管理,有以下优点: 节省内存. 字符串比较效率高.(比较哈希值) 问题: 相同的字符串共享同一份内存么? 相同的长字符串一定不共享同一份内存么? lua字符串如何管 ...

  9. scala 隐式详解(implicit关键字)

    掌握implicit的用法是阅读spark源码的基础,也是学习scala其它的开源框架的关键,implicit 可分为: 隐式参数 隐式转换类型 隐式调用函数 1.隐式参数 当我们在定义方法时,可以把 ...

随机推荐

  1. Django 视图笔记

    视图 概述 作用:视图接受web请求,并响应 本质:python中的一个函数 响应: 网页;重定向:错误视图(404.500) json数据 url配置 配置流程 1:指定根基url配置文件 sett ...

  2. 10行Python代码计算汽车数量

    当你还是个孩子坐车旅行的时候,你玩过数经过的汽车的数目的游戏吗? 在这篇文章中,我将教你如何使用10行Python代码构建自己的汽车计数程序. 以下是环境及相应的版本库: Python版本 3.6.9 ...

  3. Kannada-MNIST:一个新的手写数字数据集

    TLDR: 我正在传播2个数据集: Kannada-MNIST数据集:28x28灰度图像:60k 训练集 | 10k测试集 Dig-MNIST:28x28灰度图像:10240(1024x10)(见下图 ...

  4. 【Springboot】实例讲解Springboot整合OpenTracing分布式链路追踪系统(Jaeger和Zipkin)

    1 分布式追踪系统 随着大量公司把单体应用重构为微服务,对于运维人员的责任就更加重大了.架构更复杂.应用更多,要从中快速诊断出问题.找到性能瓶颈,并不是一件容易的事.因此,也随着诞生了一系列面向Dev ...

  5. HIT软件构造课程3.2总结(Designing Specificaton)

    本节转向“方法/函数/操作”如何定义 上一节是名词 这一节是动词 1.编程语言中的函数和方法 方法 使用者不需要知道方法内部如何实现,这叫做“抽象”. 参数 参数类型是否匹配,在静态检查时完成. 返回 ...

  6. 一文彻底读懂MySQL事务的四大隔离级别

    前言 之前分析一个死锁问题,发现自己对数据库隔离级别理解还不够清楚,所以趁着这几天假期,整理一下MySQL事务的四大隔离级别相关知识,希望对大家有帮助~ 事务 什么是事务? 事务,由一个有限的数据库操 ...

  7. Java序列化机制剖析

    本文转载自longdick的博文<Java序列化算法透析>,原文地址:http://longdick.iteye.com Java序列化算法透析 Serialization(序列化)是一种 ...

  8. Python第三方包之pretty-errors

    Python第三方包之pretty-errors 发现了一个第三方好用的python包,这个包可以让我们在面对冗长的错误时候能够一眼看到重点 安装方式 pip install pretty-error ...

  9. jmeter实现接口关联的两种方式:正则表达式提取器和json提取器看这篇就够了

    一.前言在开展接口测试或者是接口面试的过程中,我们会发现很多接口需要依赖前面的接口,需要我们动态从前面的接口返回中提取数据,也就是我们通常说的关联. 关联通俗来讲就是把上一次请求的返回内容中的部分截取 ...

  10. codeforces 466c(暴力枚举)

    题目链接 思路如下 *题意: 给定一个序列,问有多少种方案可以将此序列分割成3个序列元素和完全相同的子序列.(子序列不能为空).即问有多少个点对(i,j)满足a[1]+-+a[i-1]=a[i]+a[ ...