Java并发系列「1」-- 并发的特性;

@TOC# Java并发系列
记录在程序走的每一步___auth:huf


Java并发系列开始写作了; 热门框架系列也会持续更新;
并发系列需要一定的计算机硬件基础; 也需要一定的Java基础; 如果连进程跟线程都不了解的情况下; 不建议直接学习并发;学习需要循环递进;

一张图解释 并发与并行

在这里插入图片描述
我们知道了什么是并发; 那么我们就开始说一下并发的特性;

创建一个Demo 复制黏贴即可使用

package com.huf;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
 *
 * auth:huf
 */
public class ThreadTest {
    //使用包装类型 Integer 可以保证其可见性 因为里面用了 final
    private Integer count = 0;
    // 关键字上加 volatile 可以保证其可见性
    private Boolean flag = true;
    public void refresh() {
        flag = false;
        System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);
    }
    public void load() {
        while (flag){
            count++;
            //ThreadTest.getUnsafe().storeFence();//storeFence 可以保证其可见性
            //synchronized(this){}//可以 保证其可见性
            //Thread.yield();//上下文切换 可以保证其可见性
            //System.out.print("");//很神奇吧? print可以保证可见性
        }
        System.out.println(Thread.currentThread().getName()+"跳出循环:"+count);
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadTest t = new ThreadTest();
        new Thread(()->t.load(), "threadA").start();
        Thread.sleep(1000);
        new Thread(()->t.refresh(),"threadb").start();
    }

    public static Unsafe getUnsafe(){
        try {
            Field field  = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

并发有三大特性 :
我个人是这么理解的 (并发的三大问题)【有序性】【可见性】【原子性】 本章节主要讲解线程可见性

可见性 :如何保证其可见性?

什么是可见性?
当一个线程修改了共享变量的值,其他线程能够看到修改的值。Java 内存模型是通过在变量 修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介 的方法来实现可见性的。

我们先讲下内存模型 加深理解一下可见性

JMM 内存模型

什么是JMM :Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各 种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效 果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可 以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。JMM 描述的是一种抽象的概念,一组规则,通过这组规则控制程序中各个变量在共享数据区域和私 有数据区域的访问方式,JMM是围绕原子性、有序性、可见性展开的。

我把上面那个程序demo做成图;
在这里插入图片描述

我们基于上面那个图 的顺序:

主要场景:
	我们主线程  开启《线程一》执行 load()  那么 while (flag) 就会一直循环; 
	《线程一》会一直从本地内存中读取flag 
	此时我们开启 《线程二》再执行refresh() ,改变了变量flag 变量flag刷进去主内存
	...(后面有很多种场景)
第一个种情况:我们什么都不做, 那么《线程一》就会一直执行下去;
因为两条线程是互不干预的; 《线程一》 一直从自己的本地内存里面读取flag 
即使《线程二》改变了flag的值 并且刷新到了主内存中。 《线程一》也是看不见的; 
这种情况叫做---》 不可见;

(我们主要是为了解决这个问题; 以下是解决方案)
我们主要要把本地内存 的数据 让它被淘汰 或者 让 线程在本地内存中读取不到数据;
那么就可以保证数据的一致性;


把int 变成 Integer

在这里插入图片描述
我们点开源码 即可发现;
在这里插入图片描述
final 保证其可见性,一下是执行结果:
在这里插入图片描述


变量上加上关键字 volatile

在这里插入图片描述
volatile为什么会保证其可见性? 注 : volatile还有其他作用;在其他文章还会继续阐述

volatile:
在汇编成层面上:
volatile在内部实际上使用lock前缀指令;【lock指令不是内存屏障;但是可以达到内存屏障的效果;】
注 : volatile还有其他作用; 在其他文章还会继续阐述 volatile; 实际上在不同层面上volatile 有不同的解释;
在JVM层面上:
volatile 实际上是通过 storeload 调起了Fence() 内存屏障
我们既然知道了 storeload 那么 他们方法一共有四种
也比较有趣 : storestore,storeload,loadload,loadstore

  1. 在每个volatile写操作的前面插入一个StoreStore屏障
  2. 在每个volatile写操作的后面插入一个StoreLoad屏障
  3. 在每个volatile读操作的后面插入一个LoadLoad屏障
  4. 在每个volatile读操作的后面插入一个LoadStore屏障

使用storeFence

在这里插入图片描述

storeFence:调起内存屏障 直接让本地内存失效;


使用 synchronized

在这里插入图片描述
synchronized :
底层实际上也是调用storeFence 拉起内存屏障去保证变量的可见性


Thread.yield()

在这里插入图片描述
Thread.yield():
上下文切换 以下图为例 我们cpu进行切换的时候 会把当前线程的所有状态进行保存; 包括当前运行到第几行代码等等; 这样下次可以直接到CPU切换回来的时候 回去总线找 上次执行的线程上下文数据地址;然后去内存地址中把数据load回来 然后继续往下执行;
Registers: 就是寄存器
Cache: 当前CPU缓存
ALU: 算法执行器(可以支持多个种基本算术和按位逻辑函数)

这里总结一句话:一旦当前线程 上下文切换了 下一次 我们demo的 flag 就会从内存中读取; 这样就可以保证有序性
在这里插入图片描述

题外话: 我们的print 实际上就是内部 用了synchronized 保证了线程的可见性;
在这里插入图片描述


我们总结一波;

总结

如何保证多线程的可见性:
1:使用 volatile
2: synchronized关键字
3:使用storeFence;
4: Thread.yield()
… (有非常多的方式)

往底层再次总结:

1. JVM 的storeFence 内存屏障

2.cpu的上下文切换 Thread.yield()


Seeyou

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Like Java Long Time

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值