栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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


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


我们知道了什么是并发; 那么我们就开始说一下并发的特性;

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

package com.huf;
import sun.misc.Unsafe;
import java.lang.reflect.Field;

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
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/677243.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号