多线程简介

内存模型

众所周之,Java是有个内存模型的,也就是堆区,栈区,本地方法区,方法区,程序计数器。
其目的是为了让Java能够在不同平台运行的效果一致。

这个只是其表面状态,就像面向对象编程,是给你看到的一层假象,就算你是面向对象编程,CPU执行命令时还是顺序执行,遇到特定指令跳到对应位置执行,执行结束后返回指令的下一行。

那其真谛是什么?
OK,接下来我们一起给JVM内存模型脱一脱衣服。

与内存聊天

首先cpu在执行中,执行的是高速缓存中的记录,这个高速内存就是买cpu时很关键的一个参数叫缓存,一般现在CPU都拥有三级缓存,L1、L2、L3。而高速缓存中的记录是从主内存中拿到的副本,也就是拷贝了一份。

原因是我们的CPU操作太快了,快到我们的内存根本跟不上CPU的速度,就更别提硬盘了。那CPU厂商为了能够让CPU充分利用运算能力,不去等其他硬件的配合,于是CPU就有了高速缓存,越接近CPU那一级的缓存越快,同理也越昂贵。

当CPU需要一条数据时,会把数据加载到高速缓存中,这个过程并不是把内存中的数据“剪切”到高速缓存中的,他是拿了这份数据的副本进行处理,处理完成后再回写到内存中,再由程序决定是否需要写入硬盘持久化。

那这个过程中CPU都做了哪些操作,JVM又配合了什么呢?
首先所有线程栈和堆会被保存在缓存里面,部分可能会出现在CPU缓存中和CPU内部的寄存器里面

  1. lock(锁定):作用于主内存的变量,把一个变量标识变为一条线程独占状态
  2. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
  6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接受到的值赋值给工作内存的变量
  7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
  8. write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中

这段话我相信大家在很多网站上也见过,可能每个人描述不一样,但大同小异。
需要注意的是,如果没有运算后第一步 赋值到工作区的操作 是不允许写入主内存区
其作用的顺序为-> Lock(锁定) -> Read(读取缓存区) -> Load(加载到工作内存) -> Use(使用) -> Assign(回写到工作内存) -> Store(存储到缓存区) -> write(回写到主内存) - Unlock(解锁)

笔记:线程的本地内存,指的是cpu的缓存和寄存器,JVM的静态内存模型,他只是一种对内存模型的物理划分而已,只局限在内存,而且只局限在JVM的内存,线程共享变量一定是在主存中的

所以共享变量的流程一定是线程A从本地存储写入变量到主存,线程B去读这个变量
这问题不就出现了吗。 如果B刚读完这个变量到自己的本地内存中,A把这个变量改了,该怎么办?

线程并发处理

上面提到的问题,就涉及到了并发处理了,数据不一致,下面以程序举例子(代码有精简):

private static int clientTotal = 50000;
private static int count = 0;

ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("test-%d").build();
ExecutorService service = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS,
      new LinkedBlockingDeque<>(1024), factory, new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < clientTotal; i++) {
    service.execute(() -> {
        add();
    });
}

private static void add(){
    count ++;
}

这个程序一定是有问题,可以拿出来试一下,如果电脑性能特别好,可以多尝试几次,相信最终结果肯定不是50000。
因为我们是多线程程序,线程并发同时处理,假设A、B同时拿到0这个值,那计算完都是1,回写到主存中都是1,也就是一次计算失效了。
为了解决这个问题,我们可以通过Atomic包下的类来处理,Atomic包下的工具类实现了CAS[CompareAndSwap]算法。

CAS的主要原理为:
获取主存的值,do while方式循环判断当前要被更改的值是否与线程处理前获取到的值相同,
相同则允许更改,不相同循环重新在底层内存中取值计算

CAS只是其中一种处理方式,另外还有synchronized同步、Lock锁、wait和notify、特殊域变量volatile、ThreadLocal局部变量、阻塞队列等等方式,印象中Java并发容器中就有使用阻塞队列的容器,具体是哪个有点记不清了,有时间拿出一块时间专门写一个并发容器的总结。

CAS这种方式并不是很好,原理有二,第一,CAS会占用资源去做循环,第二,会发生ABA问题,我个人很讨厌用这种名次来说事,但这个我还没有找到其他方式来描述

CAS中ABA问题

所谓ABA问题,回过头看CAS原理:do while方式循环判断当前要被更改的值是否与线程处理前获取到的值相同
那问题就来了,仔细细考一下,如果我A线程计算特别快,同样一次计算,A线程用时2ms,B线程用了4ms
这时候等B执行完,A都执行两次了,如果初始值为0,A线程第一次计算,+1,第二次计算 -1,此时主存中的结果仍为0
A线程第二次计算结束后,B线程终于执行完了,一看,主存中还是0,跟他拿到的计算前的值相同,乐呵呵的就把他计算的值放主存里了

打眼一看,这个结果貌似也还可以接受。但如果遇到特殊场景,需要针对这个问题进行处理.
比如有个变量是记录发送消息个数的,A线程发送,B线程统计,AB线程恰好第十条数据同步,A线程发送第10条数据,失败了,第十一条数据成功,此时记录消息的变量为10,线程B记录完第9条数据,恰好读到第十条数据,此时B线程认为,A线程一共发了10条,10条成功。那这个情况是不允许发生的。 ---场景仅供参考

为了解决这个问题,最简单的办法还是去Atomic包,去找AtomicXXXXXXXFieldUpdater,这个处理方式为,会在存储时增加一个东西,就是版本,比较值是否相同时还会比较小版本是否一致

但这个弊端就是,方法在高并发情况可能会增加CAS失败率,造成更多循环,浪费资源,这个是没法避免的

总结

其实线程这块,主要就那么几块,内存,内存包括安全发布,可见性,还有J.U.C,原子性包,线程那几种方式再加上信号量那些概念,同步相关,包括synchronized同步,Lock锁,上面提到的那些,剩下的也就没啥了,搞懂这些,估计在遇到啥也不是问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • iOS系统 中,每一个应用都是一个进程。具体了解Runloop底层原理:https://www.jianshu.c...
    我叫Vincent阅读 486评论 0 3
  • 以上代码会重复运行 , 不会停止。 JMM(java内存模型) 若想学习好多线程, 那么必须了解一下JMM Jav...
    尼尔君阅读 1,745评论 0 2
  • 九种基本数据类型的大小,以及他们的封装类。(1)九种基本数据类型和封装类 (2)自动装箱和自动拆箱 什么是自动装箱...
    关玮琳linSir阅读 1,874评论 0 47
  • 话不多说,上代码 Objective-C 实现 对以下数组的排序结果如下: Swift实现(swift2.0)
    深山问阅读 1,574评论 1 2