并发编程

并发编程

1.synchronized关键字的底层原理是什么?

其实synchronized底层的原理,是跟jvm指令和monitor有关系的

你如果用到了synchronized关键字,在底层编译后的jvm指令中,会有monitorenter和monitorexit两个指令 。

每个对象都有一个关联的monitor,比如一个对象实例就有一个monitor,一个类的Class对象也有一个monitor,如果要对这个对象加锁,那么必须获取这个对象关联的monitor的lock锁。

他里面的原理和思路大概是这样的,monitor里面有一个计数器,从0开始的。如果一个线程要获取monitor的锁,就看看他的计数器是不是0,如果是0的话,那么说明没人获取锁,他就可以获取锁了,然后对计数器加1。

如果一个线程第一次synchronized那里,获取到了myObject对象的monitor的锁,计数器加1,然后第二次synchronized那里,会再次获取myObject对象的monitor的锁,这个就是重入加锁了,然后计数器会再次加1,变成2

这个时候,其他的线程在第一次synchronized那里,会发现说myObject对象的monitor锁的计数器是大于0的,意味着被别人加锁了,然后此时线程就会进入block阻塞状态,什么都干不了,就是等着获取锁 。

接着如果出了synchronized修饰的代码片段的范围,就会有一个monitorexit的指令,在底层。此时获取锁的线程就会对那个对象的monitor的计数器减1,如果有多次重入加锁就会对应多次减1,直到最后,计数器是0 。

然后后面block住阻塞的线程,会再次尝试获取锁,但是只有一个线程可以获取到锁。

2.聊聊你对CAS的理解

CAS全称是CompareAndSet,(比较和设置),CAS在底层的硬件级别保证了原子性,可以使用底层是基于CAS实现的类完成一些线程同步的累加操作。

在多线程下使用synchronized关键字保证线程安全会影响性能。引入并发包下的一些其他操作如CAS(compare and set),第一步先读旧值,第二步CAS,拿旧值与当前值进行比较,如果一致则可进行修改;如果不一致说明,在第一步读取旧值后,有线程将数据修改,则此次修改失败。CAS在底层的硬件级别保证一定是原子的,同一时间只有一个线程可以执行CAS,先比较再设置,其他的线程的CAS同时间去执行此时会失败。

3.ConcurrentHashMap实现线程安全的底层原理到底是什么?

jdk1.8之前ConcurrentHashMap实现线程安全使用的是分段锁技术,即将一个大数组分成几个小数组,当并发put时,处于同一个小数组的put操作会串行;不同小数组间的put操作不受影响 。

jdk1.8ConcurrentHashMap优化了锁的细粒度,并发操作时,对数组中每一个位置元素进行CAS。当并发put时,对同一位置进行put操作,如果put失败,说明在这之前有线程对这个位置进行了put成功操作,则对这个位置上的链表或者红黑树使用synchronized加锁;对不同位置put操作是不受影响的。

ConcurrentHashMap进行put操作的时候,元素为null,则进行cas,不为null,则进行synchronized同步。

put时,对key取hashcode,然后计算要放的hash桶,如果这个桶还不存在,则cas方式去把自己弄成hash桶的首节点;如果不为null(桶已存在),则直接加锁,然后在链表或者红黑树中插入。 如果正在该节点正在扩容,则去领任务(比如领到的任务是扩容第10到第20个桶的node),然后开始协助扩容

1.8以前是多个组数进行分段加锁,一个数组一个锁.在1.8之后,优化了细粒度,对数组的每个元素进行CAS,假设,多个线程同时操作Map但不是Map中的同一个数组,那么是没有关系的.如果是多个线程同时put操作Map中的一个数组,那么只有一个会成功,其他都会失败,此时需要基于链表或红黑树来处理,使用synchronized对当前数组加锁,然后进行操作.所以只要不是同时操作同一个位置的元素就不会触发同步的串行化操作,性能不会受影响.

4.线程池的底层工作原理

首先任务过来会去判断当前线程池里面的线程数是否大于核心线程数,如果小于则新建线程执行任务。执行完任务后的线程会去阻塞到任务队列,等待任务队列中有任务到来。

如果任务过来发现当前线程池里面的线程数等于核心线程数,就将任务放在任务队列中,等待有线程来处理。

如果任务队列放满了,就去判断当前线程池里面的线程数是否小于最大线程数,如果小于就创建线程执行任务;如果等于最大线程数,且此时任务队列也放满了,就执行拒绝策略,抛异常或者是其他处理小于核心线程数,创建线程;大于核心线程数且小于设置的最大线程数,则继续创建线程执行任务;大于最大线程数,则任务进入队列;如果任务队列满了,则使用拒绝策略。任务执行完,大于核心线程数小于最大线程数的线程会被回收,只留核心线程数的线程在线程池。

5.如果在线程池中使用无界阻塞队列会发生什么问题?

调用超时,队列变得越来越大,此时会导致内存飙升起来,而且还可能会导致你会OOM,内存溢出。

6.Java内存模型

java内存模型是对计算机的一个抽象,将整个计算过程分成了6步去执行。

read操作是将主存中的数据读取到cpu的cache中。

load操作将cpu的cache中数据加载到jvm的寄存器中。

use

assign:接收到赋值指令时给工作内存变量赋值。

store将jvm寄存器中数据存储到cpu的cache中 。

store将jvm寄存器中数据存储到cpu的cache中 。

7.volatile

volatile主要是用来解决多线程场景下变量的可见性以及顺序性。 如何实现可见性呢?如果在一个线程中修改了被volatile修饰的变量值,那么会让其他线程的工作内存中的变量值失效,在下一次需要用到该变量的时候,重新去主存中去加载最新的变量值。

比如有一个data变量被volatile修饰,线程1将data的值改变,并将data修改后的值刷回主内存。它就会去将其他线程的工作内存中原本的加载的data旧值全部失效,在其他线程想使用自身工作内存中的内存值时,会发现已经失效,必须从主内存中去读取data值,这时读取的就是data修改后的值了。

8.被volatile修饰之后的变量就不会被缓存到CUP级别的缓存中了?

都会缓存到cpu缓存中的, 被volatile修饰后, 其他变量的的修改, 通过mesi协议和内存屏障作用, 当前缓存的变量会被置为失效, 这样cpu核读取变量发现是已失效, 会重新从内存中获取

9.volatile底层是如何保证可见性的?

lock指令:volatile保证可见性

对volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给CPU,CPU在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,所以各个CPU都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改 。

如果发现别人修改了某个缓存的数据,那么CPU就会将自己本地缓存的数据过期掉,然后这个CPU上执行的线程在读取那个变量的时候,就会从主内存重新加载最新的数据了 。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值