并发编程学习笔记

为什么学?·

只要去做一个技术含量稍微好点的系统,并发包下面的东西是很容易会要用到的

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值