多线程与高并发

线程基础知识

线程的历史-CPU性能压榨的血泪史

在这里插入图片描述
io课

大厂必问_什么是进程线程纤程(GO12课有、线程池基础)

进程:

静态单位:资源分配的基本单位,分配资源
qq.exe被执行1次,程序被加载到内存叫进程
执行以线程执行主线程

线程

通俗角度理解什么是线程

程序不同的执行路径是线程

  • 只有一个线程在运行
    在这里插入图片描述
  • 一个线程出现了分支,有不同的线程做不同的事
    在这里插入图片描述
从底层角度理解什么是线程

调度执行基本单位,多个线程共享进程这个资源

纤程/协程

程序

可执行文件
在这里插入图片描述

什么是线程的切换

程序:指令数据
cpu:计算(指令)、寄存器组(数据)、pc寄存器(算到哪儿)

  • T1线程执行一半切换到T2就先把T1放到缓存里执行T2,线程切换时操作系统决定;
    在这里插入图片描述
单核CPU设定多线程是否有意义

同个时间段只能跑1个线程,A线程等待数据状态时不消耗CPU,可以运行B线程,充分利用CPU资源;

  • cpu密集型:cup做大量计算;
  • io密集型:大量io做等待;
线程数是不是越大越好_1

不是;
线程切换要消耗资源,10000个线程cpu消耗在线程切换上;

实验

多线程分段处理大量数据集合

线程数设多少最合适_1
  1. 压测来决定;
  2. 看机器配置几核,每个核都用上效率最高,但是为了安全最多占用80%给别的程序留空间,假设合适的值在压测来决定;
    不一定每个核都充分利用,因为有操作系统和java程序
    在这里插入图片描述
线程设定公式

在这里插入图片描述

  • W/C:
    W:等待时间
    C:计算时间
    两个数的比值:1/1
  • Ucpu:期望cpu利用率:100%
  • Ncpu:核心数:1
  • 11(1+1)=2只需要2个线程
怎么知道cpu的等待时间和计算时间
  • 部署上去,运行之后,通过一些统计才能知道;
  • 通过性能分析工具测算:profiler、jprofiler、arthes(阿里)、调用链路追踪;

阶段小结

  • 压榨发展历史
  • 底层理解线程
    线程调度
  • 什么是程序线程进行的概念基础知识

面试题:创建线程的5种方法

Thread_Runnable_Lambda

用Runnable创建比较合适;
在这里插入图片描述

使用ThreadPool

在这里插入图片描述
线程池就是一个池子,里边有好多线程

线程池和Callable

在这里插入图片描述
通过泛型指定带返回值的任务执行返回什么类型;
Future:异步的概念;
Future.get()是阻塞方法,什么时候返回值什么时候往下执行;

运用FutureTask

在这里插入图片描述
FutureTask实现了RunnableFuture接口,
RunnableFuture接口继承了Runnable的实现了run()方法可以运行,
RunnableFuture接口既是Runnable又是Future,自己可以运行结果也可以装在自己里

  • 用法
    在这里插入图片描述
    1. 创建FutureTask(把Callable扔进去)
    2. 创建Thread(把FutureTask扔进去)
    3. 启动线程
    4. 获取返回结果FutureTask.get()

阶段总结

在这里插入图片描述
最终都是new Thread()对象,线程池源码也是new Thread()对象

线程状态(问的不多)

6种线程状态的简介

  1. NEW:线程刚刚创建,还没有启动
  2. RUNNABLE:可运行状态,由线程调度器可以安排执行
    分为2种:1、可以运行;2、正在运行;
  3. WAITING:等待被唤醒
  4. TIMED WAITING:指定时间自动唤醒
  5. BLOCKED:被阻塞,等着拿锁,经过系统调度的,
  6. TERMINATED:线程结束

线程状态迁移简介

在这里插入图片描述

  1. 对象创建出来run()还没start()是NEW
  2. start()进入RUNNABLE
    正在运行是RUNNING
    cpu线程切换yield方法
    可以运行的状态但是没被运行READY,需要yield切换成RUNNABLE
  3. 等着拿锁进入同步代码块是BLOCKED阻塞状态
  4. 被这被唤醒WAITING,非阻塞状态
  5. 等着指定时间到了自动唤醒TIMED WAITING,非阻塞状态

NEW_RUNNABLE_TERMINATED

在这里插入图片描述

WAITING_TIMEDWAITING

在这里插入图片描述

BLOCKED

在这里插入图片描述
申请不到锁,正在等待拿锁;

线程状态在Lock和synchronized的区别

在这里插入图片描述

  • 等待拿锁的状态不同:
    synchronized:BLOCKED
    Lock:WAITING 忙等待
  • 只有synchronized是BLOCKED的状态是由系统调度的,其他的状态都是WAITING或者TIMEDWAITING,

park之后的线程状态

在这里插入图片描述

线程状态阶段总结

大图

线程“打断”inerrupt(中级:容易混淆)

线程打断3方法

在这里插入图片描述

  • 设置标志位
  • 查询标志位
  • 查询标志位并重置标志位

interrupt_and_isInterrupted

在这里插入图片描述

interrupt_and_interrupted

在这里插入图片描述

interrupt_and_sleep

在这里插入图片描述
线程睡眠时可以打断的,不过会异常然后打断默认重置,然后看你怎么处理。

interrupt_and_wait

在这里插入图片描述
和上面一样,异常然后看你怎么处理

interrupt_and_synchronized

锁竞争:synchronized设标志位不能干扰争抢锁,不会抛异常,设置标志位该抢锁还是继续抢锁
在这里插入图片描述

interrupt_and_lock

设标志位不能干扰lock锁;

interrupt_and_lockInterruptibly

lockInterruptibly可以被打断的过程,抢锁的过程盯着标志位,有人打断我抛异常,cache住逻辑交给程序员;
在这里插入图片描述

interrupt_阶段总结

在这里插入图片描述

线程的“结束”(有可能问道)

问题:如何优雅的结束一个正在运行的线程

服务器24小时跑着,不能直接就打断,那样有客户端连着,一些数据就没了;

使用stop方法结束线程(废了)

在这里插入图片描述
非常粗暴,正在运行着二话不说直接停止了,容易产生数据不一致的问题;

为什么不建议使用stop方法?

太粗暴了,不管线程啥状态直接就干掉了,释放所有的锁,容易产生数据不一致的问题;

使用suspend_resume方法暂停/恢复线程(废了)

在这里插入图片描述
suspend:暂停
resume:恢复

  • 和stop是用样的原因

为什么不建议使用suspend_resume

暂停的时候正在持有一把琐,容易产生死锁的问题;

volatile结束线程

在这里插入图片描述

  • 不依赖循环里的中间状态
    wait()阻塞,
  • 不能控制时间

interrupt(线程自带的标志位)结束线程

在这里插入图片描述

检查标志位被设定了就退出,比较优雅一些,循环一次检查一次标志位,

阶段总结

在这里插入图片描述
比如给服务器上传大文件,想终止就只能用3和4,需要子线程和外面的线程相结合就需要用到锁,后面会讲;

并发编程三大特性简介(必问)

可见性
有序性
原子性

并发编程之可见性

从一个程序谈起

可见性的基本概念

在这里插入图片描述
程序启动running读到主内存中,线程缓存了一份,t2修改的是它缓存的running所以t1读不到;
线程改了值其他线程看不到,除非主线程改了内存中的值其他线程才会看到;

用volatile保障可见性

修饰这块内存,对于任何的修改立马刷新到主内存,其他线程立马可见;在这里插入图片描述

04_某些语句触发内存缓存同步刷新

在这里插入图片描述
system.out.pringln()触发了可见性机制,源码有synchronized同步锁可以保持可见性;

volatile修饰引用类型

在这里插入图片描述
在这里插入图片描述

三级缓存_01

在这里插入图片描述
在这里插入图片描述

缓存行的基本概念

  • for循环时候读1个值,会把相邻的值也读到缓存里,这一块数据叫缓存行,
    在这里插入图片描述
  • 缓存行,一行是64个字节,读x会把y也读进来,在这里插入图片描述

局部性原理

都到某一个数据,相邻的数据很快能读到,

  • 空间局部性原理:用到一个值会读到周边的值,
  • 时间局部性原理:指令的问题,读指令一次性读很多的指令到缓存,

通过程序认识缓存一致性_01

缓存一致性和volatile没有任何关系;
t1修改了x需要通知t2,t2修改了y需要通知t1,因为xy在同一个缓存行里,所以速度慢;
在这里插入图片描述

  • 想要快就用其他空的变量填充把64字节占满(真的会有人这样写程序 jdk1.7 Linked Blo...Queue 这个类);
    在这里插入图片描述

认识Disruptor(效率最高的MQ单机版)中缓存行对齐的写法

  • 框架:闪电(效率最高的MQ单机版)
    在这里插入图片描述
    装消息都有个缓存,MQ是环形的缓存有1个指针围着来回转,
    在这里插入图片描述

认识Contended写内部类用

痛点:如果缓存行变成128字节,代码还得修改;

jdk1.8有个注解,被注解标注的数据单独占一行,
在这里插入图片描述

注意:默认@Contended是被限制的,JVM运行需要加参数才会起作用;在这里插入图片描述

认识硬件层面的缓存一致性

MESI英特尔CPU的协议,我这个缓存行数据别的CPU也有缓存,我会主动监听缓存行数据有没有被修改;
CPU

为什么缓存一行是64字节?

在这里插入图片描述
工业实践得到的最高实践,折中值64字节;

阶段小结

可见性:volatile线程本地的换成互相保持缓存一致性的机制
缓存概念 123 缓存行
缓存一致性协议

并发编程之有序性

并发编程之有序性_问题的提出

程序真的是按“顺序”执行的吗?

乱序的验证/分析

为何会存在乱序

为了提高效率,指令1等待返回的时候执行指令2,充分利用cpu;XI

乱序的原则

指令可能换顺序执行
不影响单线程的最终一致性
在这里插入图片描述

通过一个小程序认识可见性和有序性_01

在这里插入图片描述
先执行了true,后执行了number那么输出就是0,

对象的半初始化状态_01

  • 5条java汇编指令构成
    申请分配内存空间
    特殊调用默认构造方法
    建立关联
    构成
  • 对象的创建过程
    new完是半初始化状态m=0,成员变量是默认值;
    invok…是构造方法m=8
    astore…是建立关系
    在这里插入图片描述

this对象逸出_01

可能输出中间状态值0
在这里插入图片描述

解决:不在构造方法启动线程

构造方法启动完在启动线程在这里插入图片描述

总结

在这里插入图片描述
下面是有序性解决

happens-before原则

JVM级别是java虚拟机,对java汇编语言做了约束有8条约束,一条lock锁底层都实现了;

CPU用屏障指令阻止乱序(jvm不是用的cpu的屏障指令)

每种cpu的屏障指令都不一样
内存屏障:中间加个隔层不让越过去,
在这里插入图片描述

JVM要求实现的四种屏障

实现jvm层级必须实现这4种机制,不想重排序加volatile就可以;
在这里插入图片描述

用volatile禁止指令重排

保持线程可见性

禁止指令重排序

volatile修饰的变量指向的内存空间进行内存屏障;别人读完我在写,别人写完我在写,我写别人不能读;
在这里插入图片描述

volatile在hotspot虚拟机中的实现

CPU底层有实现
JVM虚拟机有实现
加了volatile->class->acc_volatile->
在这里插入图片描述
在这里插入图片描述

有序性总结

并发编程之原子性(最复杂)

从一个小程序认识原子性的概念(一)

在这里插入图片描述
因为n++产生竞争,中途被别的线程打断了,导致数据不一致,n++需要原子操作
在这里插入图片描述

n++汇编为什么会被打断?

获取>压栈>加值>输出
这只是jvm的汇编就要5条,在翻译成CPU的可能会更多条,所以容易被打断;
在这里插入图片描述

n++上锁

synchronized保证了数据的可见性和原子性;
在这里插入图片描述

底层原子性和JVM原子性(一)

在这里插入图片描述

  • 语言都要编译成CPU的汇编语言,
    看汇编手册才能知道哪些是原子性;

  • java有8大原子操作,java虚拟机规定了;
    java判断不了就上锁

用上锁保证原子性

上锁就把代码块看作一个整体,执行的之后不能被打断;

上锁的本质(一)

  • 把并发变成序列化操作,效率会变低,因为抢的是同一把锁子;
    并发:三个人上同一个厕所,2秒钟全部方便完了;
    序列化:一个人先上厕所,然后锁上门,其他人在外面等着;
    在这里插入图片描述
  • 不上同一把锁
    在这里插入图片描述

一些同步的基本概念_锁的粒度

锁:某个对象当作锁;
临界区:锁定的代码块;
锁定了某个对象,持有这把锁才能执行;
在这里插入图片描述

小结

访问同一个数据,产生竞争条件>产生数据不一致>数据同步上锁>上锁的代码叫临界区

  • 上锁:保障临界区里的原子性(2种方式:悲观、乐观)

悲观锁与乐观锁

在这里插入图片描述

  • 悲观锁
    比较悲观,总觉得会被别人打断,有没有人我都要锁上;
  • 乐观锁
    无锁、自旋锁、cas

CAS的概念解析

先把值n=0读过来,写回去n=1的时候看一下是不是原来那个值,如果不是那就把现在的值n=5读过去,写回去n=6看下是不是n=5,如果不是那就再来一次直到成功,所以称为无锁;
在这里插入图片描述

CAS的ABA问题

  • 在对象之间引用需要注意:
    此0非彼0:A改成了B又改成了A
    0读过来写1,读还是0,可能这个0被别的线程改8又被别的线程改0;
  • 解决:加version任何操作都+1,就知道是不是之前的值(时间戳、数字、版本);

CAS的底层原子性保障

CAS操作本身就是原子性的

通过Atomic类深入认识CAS

最终用的是cas乐观锁,调用本地native代码,底层是c++实现的,没有用同步锁;

深入Hotspot代码深入理解CAS

is_MP();是多核的就加lock,缓存锁或总线锁看情况而定,在底层还是有一把琐;
在这里插入图片描述

答疑与阶段小结

单核不用lock,自己不能打断自己的指令;

乐观锁与悲观锁的效率谁更高

  • 实战就用synchronized同步锁;

悲观锁会有队列等待抢这把锁;等待的线程不消耗CPU资源;
乐观锁是while转圈的循环和线程切换,原地打转消耗CPU,消耗资源多于悲观锁;
在这里插入图片描述
临界区长,等的时间长就用悲观锁;
临界区短,等的时间短就用乐观锁;

synchronized和三大特性

  • 可见性
    unlock解锁时候,会对内存刷新,第二个线程才会开始执行,有内存屏障;保证不了有序性
    在这里插入图片描述

synchronized锁升级深入详解

多线程与高并发(一)

多线程与高并发(二)

多线程与高并发(三)

多线程与高并发(四)

多线程与高并发(五)

多线程与高并发(六)

多线程与高并发(七)

多线程与高并发(八)

多线程与高并发(九)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值