本期内容包括
JUC多线程并发、JVM和GC等目前大厂笔试中会考、面试中会问、工作中会用的高频难点知识。
斩offer、拿高薪、跳槽神器,对标阿里P6的《尚硅谷_互联网大厂高频重点面试题(第2季)》发布。本套课程总结分析了2019年大厂互联网公司常见常考的技术点,通过对40多个题目共计120集视频详细全面的讲解,让大家深刻掌握、扎实吃透当前的主流Java高级技术。
JUC多线程及高并发
并发和并行有什么区别
并发:多个线程去访问同一个资源
并行:各种事情同时去做,一边干什么,一边干什么
JMM:java内存模型
各个线程对主内存中的数据进行改变,不是直接修改,而是会把age=25拷贝到自己的工作内存中再进行改变
t1改为37后要把新数据写回到主内存中,t2,t3不知道主内存中的值已经改变了
所以我们需要有一个机制:JMM内存模型的可见性,只要有一个线程改变数据后要写回到主内存中,其它的线程马上就会知道主内存中的数据已经改变了
3秒之后,a线程已经把number改了,但是main线程不知道,对main线程不可见,还在傻傻的等着,没有人通知我
原子性:不可分割、完整性,也就是某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败。
把验证可见性的代码写到一个方法seeokbyvolatile
20个线程去调用addplus方法,每个线程调用1000次,则结果应该是2000。
多线程编程的模板forthread10是自己设置的快捷键
轻量级就是乞丐版的sunchronized
不要杀鸡焉用牛刀,太重了
拷贝回自己的内存空间,每个人都拿到0,写回到主内存时,线程1写回到的时候被挂起了,线程2歘的写回了。然后线程1恢复后又写回了一遍,把原来的1给覆盖了。
三个线程都拿到1,都在各自的工作内存中加1,写回到的时候,没有拿到最新的值就又写了,写覆盖
不可以,必须考虑指令之间的数据依赖性
加了volatile之后是禁止指令重排
但是指令重排后,答案可能是5
所以在这些变量前面加上volitaile就可以禁止指令重排
单例模式有六种
掌握一种即可
第一步 定义私有的实例变量
第二步 构造方法
第三步 新建,返回同一个变量
比较引用类型,内存地址
在多线程坏境下,单例模式出现了问题
如果加上synchronized,在多线程的环境控制住了
但是太重了,并发性下降了
DCL模式 Double Check Lock双端检锁机制
在加锁之前和之后都进行一次检测
地址不为空,但是内容为空
所以要在instance变量上面加上volatile
总结:
CAS compare and set 比较并交换
多个线程去操作主内存中的数据。
一个叫做期望值、一个叫做更新值
主内存中数据是5
一个线程拷贝回去自己的工作内存,对它进行修改,然后写回到主内存的时候,会进行比较和交换,如果和拷贝的数据一样的话,就将改变后的数据写回去;否则的话,就不进行写回。
AtomicInteger 的getandincrement方法底层其实是CAS思想+套的是unsafe类的CPU原语来保证原子性,
底层思想是比较并交换,真实值和期望值相等就交换成功,否则就失败,失败就再来,直到比较成功为止。
2号线程比1号线程快,把原来的A改为B,又改为A。
1号线程回来后,期望的和原来的一样,以为没有改变过,于是写回主内存。
但是中间有猫腻,2号线程已经把它改过了又改回去了。
ArrayList底层是一个数组
什么类型的数组?
数组初始长度为10,超过10以后进行扩容。
java.util.ConcurrentModificationException
并发修改异常
线程不安全是指:在多线程的情况下,
方法一:
Vector类可以解决这个问题,加锁一致性可以保证,但是并发性急剧下降。
传值还是传引用
age属于main方法的,然后调用方法时复印了一份传给它,然后方法把复印件给改动了
我只是给你复印了一份值,原件根本没动,所以第一个age还是20
person是main的,传引用传内存地址给方法,两个引用指向了同一个地址,这时把这个地址的值改动了
str是属于main方法的,这个池子里有了abc
这个池子里面没有xxx,那么就重新创建一个指向它
无参数:不公平 允许某个同学突然出来加塞
有参数:公平 队列先来后到
synchronized等同于锁,非公平锁
被它抢到了锁上了
线程操作资源类
t1线程在外层方法获取锁的时候,t1在进入内层方法会自动获取锁
只要锁匹配,几把锁都可以。
如果是第一次进来线程,就不进循环
A线程进来,发现期望的是空的,那么while的条件就是false,于是不进入循环,直接拿到了锁。
B线程进来,发现期望的值不是空,那么while的条件就是true,于是它进入锁中,一直会循环的判断,直到期望的值是空了,才能推出循环,获得锁。
BB等5秒钟,等A解锁了,B才能解锁
以前使用锁和synchronized读和写通通不能并发执行,数据一致量可以保证,但并发性急剧下降。
循环屏障
人都到齐了才能开会
一个是减法,倒计时
一个是加法,累积
多个线程强多个资源
信号灯
资源为1时就退化成synchronized
不写是非公平锁,效率高允许加塞
可以复用
可以抢占可以释放
停3秒钟
生产者 消费者 sychronized wait notify
队列:先进先出
阻塞:
生产者消费者模式:用阻塞对垒写
阻塞的意思是:我现在满了,就等着,直到有元素出去。因为我不能丢消息呀,就等着
取不出来就堵着
过时不候
超时
这时候没有等2秒钟
只阻塞2秒钟。2秒钟之后就打印出false
0库存,生产出来的马上被别人拿走
你不消费,你想到里面插第二个你插不进去
防止虚假唤醒
没有控制住,生产了2个
所以唤醒的时候要加入循环
区别:
runnable接口没有返回值,不会抛异常,实现run
callable接口有返回值,会抛异常,实现call
要的是runnable,有的是callable,如何把它们之间加上关系
生活的例子:两个同学,我默认接收的参数是张,但是,要找一个中间人
FutureTask实现穿针引线的作用。
已经有runnable接口,为什么还需要callable接口呢?
多个线程都要使用cllable,每次都要返回一个成功或者失败的返回值。
适配模式。
get()建议放在最后
因为我们不会等这个线程,给它充足的时间去计算
如果把get放到前面,mian线程就被堵住了
所以这里可以加一个循环的判断!等算完了,才往下做
两个线程都开始做同一个任务,只会执行一次!即复用
如果说非要进去!那么就要启动多个futuretask
ThreadPoolExecutor
辅助工具类
一池固定线程、一池一线程、一池多线程
最高几个客户来办业务,就报拒绝了
默认的拒绝策略:马上报异常
调用者运行机制:不会抛弃任务也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
谁让你调用我的,那么你就去找他
抛弃队列中等待时间最久的任务
这样的话只能完成8个人的业务,剩下的就直接丢弃了
单线程不可能有死锁!没人跟你抢,你怎么会死锁。
两把锁