为什么学?·
只要去做一个技术含量稍微好点的系统,并发包下面的东西是很容易会要用到的
synchronized(Object){
}
主要是用来给对象加锁
synchronized底层原理
首先监视器的计数器是0,然后线程一访问,会将值改为1,此时线程2来访问只能被阻塞,等线程一放弃锁,计数器变为0以后才能访问
锁是可重入的,计数器可以不断加
CAS底层原理: CAS实现了无锁并发 保证了线程安全
取值,询问,修改
用synchronized 只有一个线程可以成功对myObject加锁,可以对他关联的monitor的计数器去加1,加锁,一但多个线程并发的去进行synchronized加锁,串行化,效果并不好,线程需要排队去执行
使用CAS 需要 new 一个Atomicinter(())
CAS在底层的硬件级别保证一定是原子的,同一时间只有一个线程可以执行cas,先比较再设置,其他线程的CAS同时间去执行会失败
意思就是说,首先读取旧值0, 当我要改的时候,我会查是否还是我读的时候的那个值,如果还是0,那就代表没有人修改,我可以进入,我将它累加为1
线程2此时 也去读取 看是不是还是跟之前的旧值相同 现在这个值 已经是1了 所以进入失败
我重新去读取现在的值 发现是1 那么我现在会再次尝试 将他的值设置成2
其实就是用一个旧值来判断需要修改的值有没有被修改过,如果有人捷足先登,那么我就获取现在的值,累加再重试
ConcurrentHashMap:
场景:多个线程过来,线程1要put的位置是数组【5】,线程2要put的位置是数组【21】map.put(vvv,hh) 明显不好,数组中有很多元素,除非是对同一个元素执行put 不然此时多线程是需要同步的。
1.7之前是分段加锁,每个数组对应一个锁,分段加锁,而1.8以后做了锁粒度的细化
一个大数组,每个元素进行put,都有一个不同的锁,如果两个线程都往数组5里面put,采取的是CAS的策略,同一时间只有一个线程能够成功执行这个CAS
他刚开始先获取数组5的值,null,然后执行CAS,线程1,比较一下,put进去我的这条数据,同时间,其他的线程执行CAS,都会失败。
其实就是用CAS来对数组中的元素进行了加锁,每个元素一把锁,按元素来分段加锁,而不是按数组, 提高了并发能力
AQS实现原理
非公平锁:
公平锁:
在CAS的进行过程中,线程的排队是依靠一个队列来完成的 即AQS,
线程1执行完代码 释放锁后,会唤醒线程2,但是默认是非公平的,如果突然半路上杀出来一个线程X来获取锁,此时锁会被X抢占
是可以将非公平锁改为公平锁的
线程池的工作原理:
系统是不会无限去创建很多的线程的,会创建一个线程池,有一定数量的线程,让他们去执行各种各样的任务,线程执行完任务后,不会销毁自己,而是让他等待执行下一个任务
我持续提交任务,只要线程池中的线程数量小于corePoolSize,就会创建新线程来执行这个任务,执行完了就尝试从无界队列获取任务,知道线程池里面有corePoolSize个线程, 接着再次提交,发现线程数量已经有corePoolSize个了,此时就直接吧任务放入队列中即可,线程会争抢获取任务执行,如果所有的线程都在执行任务,那么无界队列中的任务会越来越多
线程池核心配置参数:
corePoolSize:3
maximumPoolSize:200
keepAliveTime:60s
new ArrayBlockingQueue<Runnalbe>(200)
如果任务队列中的任务数量实在太多,那么,会创建一些额外的线程出来,但是数量不会大于maximumPoolSize
如果队列中的任务处理完了,额外的线程空闲出来了,那么超过一定的时间后,会自动销毁掉
如果河外线程去用来处理任务了,但是队列中还是满的,那么会开启拒绝策略,去抛弃掉一些队列中的任务
如果在线程池中使用无界阻塞队列会发生什么问题?
在调用服务的情况下,如果服务调用异常,可能会导致线程调用服务超时,无界阻塞队列变得越来越大,内存飙升,可能还会导致OOM内存溢出
如果线程池中队列满了,会发生什么?
有界队列,可以避免内存溢出
如果让队列满的情况下 无限制创建线程, 那么可能会因为线程过多,占用的栈内存资源过多,而导致内存资源耗尽,导致系统奔溃
即使内存没有奔溃也会使cpu load特别高
如果线程池无法完成更多的任务,那么我就自定义一个reject策略
此时我可以将这个任务信息持久化写入磁盘中,后台专门启动一个线程,后续等待线程池工作负载降低了,再慢慢从磁盘中读取之前持久化的任务,重新提交到线程池中
如果线上机器突然宕机,,线程池的阻塞队列中的请求怎么办?
必然会导致线程池中的积压的任务会丢失
要提交一个任务到线程池之前,在提交之前,我先在数据库中插入这个任务的时候,标注一下,这个任务是未提交、已提交、未完成、已完成。提交成功之后 更新他的状态是已提交状态。
系统重启后,后台线程去扫描数据库中未提交和已提交状态的任务,可以把任务的信息读取出来在重新提交到线程池中去,继续进行执行。
JAVA内存模型:
JAVA内存模型中的原子性、有序性、可见性是什么?
java内存模型->原子性、有序性、可见性->volatile->内存屏障
没有可见性:并不是 现在线程一把 num 从0改成1 线程二马上就能看到的
有可见性:具备可见性是 现在线程一修改了之后 线程二马上会去读线程一的修改 马上就能看到、
原子性:就是一次只干一件事,谁也不能影响这件事的发生,这件事是一杆到底的,是一气呵成的。
没有有序性:代码是这样的,但是在执行过程中可能会造成指令重排 重排就导致代码顺序乱了
有有序性:代码指令不会重排
volatile的原理
volatile主要是用来解决可见性和有序性的
加上了volatile关键字后,更新后会将更新的值刷回主存,并且让其他线程中缓存的值失效掉。
主要是保证变量的可见性
你知道指令重排与happens-before原则吗?
指令重排是指的 在多线程环境下 指令并没有按照原有的顺序来执行
happens-before原则:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个Unlock操作线性发生于后面对同一个锁的lock操作,比如说在代码里有先加锁 再解锁 再加锁
volatile变量规则:对一个volatile变量的写操作线性发生于读操作,必须保证是先写 再读
总结:规则制定了在一些特殊情况下,不允许编译器指令器,对程序员写的代码进行指令重排,必须保证代码的有序性、
volatile是如何基于内存屏障保证可见性和有序性的?
线程一修改缓存中的变量值->通过lock指令写回主存
其他线程->通过嗅探机制发现自己缓存的变量值已经被修改->让自己的缓存失效->下次要读的话,只能强制从主存中读取
volatile禁止指令重排 靠的就是volatile