一些自己总结的Java并发编程的知识点

1、JMM

JMM即Java内存模型,是一种抽象概念,并不实际存在。它描述了一组规范,定义了程序中各个变量的访问方式,以实现Java程序在各个平台下都能达到一致的内存访问效果。
方法中的基本类型本地变量将直接存储在工作内存的栈帧结构中;
引用类型的本地变量:引用存储在工作内存,实际存储在主内存中;
成员变量、静态变量、类信息均会被保存在主内存中;
内存模型的三大特性:
原子性:保证8个交互操作的原子性,但是会对64位的读写操作分为两步;
可见性:当一个线程修改了变量的值,会被其他线程立即得知。
volatile:会强制将变量自己当时其他变量的状态都刷出缓存;
synchronized:对一个变量解锁之前,必须把变量刷新到主内存上;
final:被final关键字修饰的字段在构造器中一旦初始化完成,并且没有发生this逃逸,那么其他线程就能看见final字段的值。
有序性:确保程序按照一定顺序执行,是的执行结果正确。
happen-before:前一个操作的产生的影响应该能被后一个操作观察到。
volatile:通过添加内存屏障的方式来静止重排序。
synchronized:同一时间只有一个线程可以访问同步代码块;

2、volatile

volatile用来解决共享变量的可见性问题:当一个线程对volatile修饰的变量进行了写操作后,会立即将该变量刷新到主内存去,同时,这个写操作还会导致其他线程中这个共享变量的缓存失效,要想使用的话必须重新去主内存中取值。
volatile用来解决指令重排序问题:
volatile写之前的操作不可重排序;
volatile读之后的操作不可以重排序
volatile的使用场景:
对变量的写操作不依赖于当前值;
该变量没有被包含到其他不变式中;
volatile实现原理:volatile的内存语言是通过内存屏障技术是现实的。编译器在生成字节码的时候,会在指定位置插入内存屏障来禁止特定类型的处理器重排序,同时内存屏障还可以保持内存的可见性。

3、CAS

CAS即比较交换,拥有三个参数(V,E,N),V表示要更新的值,表示期望值,表示新值。只有当V==E时,才将V更新为N。
缺点
ABA问题:添加版本号
可能会消耗较高的CPU
不能保证代码块的原子性。

4、synchronized

最开始被称为重量级锁,jdk1.6以后进行了优化,没有那么重,引入了偏向锁、轻量级锁的概念。
应用场景
修饰 实例方法 / 代码块时,(同步)保护的是同一个对象方法的调用 & 当前实例对象
修饰 静态方法 / 代码块时,(同步)保护的是 静态方法的调用 & class 类对象
原理:依赖于JVM和通过一个监控器对象monitor来实现的,monitor的本质依赖于底层操作系统的互斥锁
特点
在这里插入图片描述

5、可重入性 为什么synchronized是可重入锁

可重入性:当一个线程在获取锁之后还可以安全的获取该锁,不会出现死锁或者解饿的情况。
每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

6、锁升级

图片: https://uploader.shimo.im/f/Fr1Z2wbgib7MlzAe.png
偏向锁:初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程ID也在对象头里),如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
轻量级锁:当多个线程发生锁竞争的时候,偏向锁将会升级为轻量级锁。竞争成功的线程获得锁,其他线程自旋,即不断循环判断锁是否被获取成功,不会被阻塞,性能较高。长时间的自旋非常消耗资源(忙等),但是轻微的自旋相较于不断的线程切换更优。
重量级锁:自旋次数过多,说明竞争激烈,超过一定限度就会膨胀为重量级锁(默认10次),当线程竞争锁时,发现是重量级锁,将会被挂起(阻塞),等待未来被唤醒。

7、乐观锁和悲观锁

乐观锁:认为读多写少,遇到并发的可能性较低,每次去拿数据的时候都不会被被人修改,所以不上锁,只在更新的时候会判断一下是否被更新。写时先读版本号,然后加锁,失败则要重复读-比较-写这个过程,如CAS和数据库的共享锁。
悲观锁:认为写多,并发高,所以每次读写数据都要加锁,如synchronized,AQS框架下则是先城市CAS乐观锁获得锁,获得不到使用悲观锁,如reentrantLock,数据库的排他锁也是一种悲观锁。

8、公平锁和非公平锁

公平锁:线程在抢占锁失败后会进入一个等待队列,先进入队列的线程会先获得锁。
非公平锁:线程抢占锁失败后进入一个等待队列,但这些等待队列线程谁能先获得锁不是按照先到先得的顺序,而是随机的。

9、为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

new一个thread,线程进入新建状态,调用start,会启动一个线程并使线程进入就绪状态,当分配到时间片后就可以开始运行了。start会执行线程的相应准备工作,然后自动执行run方法,这是真正的多线程工作。
如果直接调用run方法,会把run当成主线程下的一个普通方法,并不会在某个线程下去执行,这不是多线程工作。
总结来说:调用start方法可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法,还是在主线程里执行。

10、AQS

简介:AQS是一个用来构建锁和同步器的框架。
核心思想:如何被请求的共享资源空闲,子将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,就需要一套线程阻塞等待以及被唤醒时锁分配到机制,这个机制AQS是CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
图片: https://uploader.shimo.im/f/3791wGCh7hYfA0E1.png
AQS维护了一个共享资源State和一个FIFO的等待队列,当有多个线程争抢资源的时候就会阻塞进入此队列。
线程在争抢State这个共享资源的时候,会被封装成一个Node节点,也就是说在AQS的等待队列里面的元素都是Node类型的对象。

11、ReentrantLock

reentrantlock类是继承了AQS、可重入的独占锁,除了具有和synchronized一样的功能外,还具有限时锁等待、锁中断和锁尝试等功能。
Reetrantlock可以构建公平锁和非公平锁。
ReetrantLock支持多个条件变量

12、ReentrantReadWriteLock

所谓读写锁,是一对相关的锁:读锁和写锁组成,读锁用于只读操作,可以由多个线程保持同步。写锁用于写入操作,是独占锁,只能由一个线程获取。
ReentrantReadWriteLock实现了ReadWriteLock接口,通过定义内部类实现AQS框架的API来实现独占/共享功能。
特点:
支持公平锁/非公平锁
支持锁重入:获得读锁的线程还可以再次获得读锁,获得写锁的线程可以再次获得写锁和读锁。
支持锁降级:写锁可以降级为读锁,读锁不可以升级为写锁。
支持条件变量,需要注意的是只有写锁可以获取条件。

13、使用线程池的好处

降低资源消耗:复用存在的线程,减少对象创建销毁的开销
提高响应速度,提高系统资源的利用率
提高线程的可管理性,统一分配,优化和监控
附加功能:定期执行,定时执行,单线程,并发数控制等

14、ThreadPoolExector

ThreadPoolExetor的构造函数包含七个参数:
corePoolSize:核心线程数,即线程池保有的最小线程数
maximumPoolSize:线程池中允许存在的工作线程的最大数量
workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被放在队列中
keepAliveTime:线程池中的线程数量大于corePoolSize的时候,如果这时候没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁;
unti:keepAliveTime参数的时间单位
threadFactory:为线程池提供创建线程的线程工厂
headler:线程池任务队列超过maxinumPoolSize之后的拒绝策略
饱和策略:如果当前同时运行的线程数量达到最大线程数量并且队列已经被放满了任务时,ThreadPoolExector定义了一些策略
AbortPolicy:抛出RejectedExecutionException来拒绝新任务的处理,默认策略
CallerRunsPolicy:调用执行自己的线程运行任务,不会拒绝任务请求。但是这种策略会降低对新任务提交的速度,影响系统整体性能,并且这个策略喜欢增加队列的大小,如果我们可以承担一定的延迟并且不能丢弃任何一个任务,可以选择该策略。
DiscardPolicy:不处理新任务,直接丢掉
DisCardOldestPolicy:丢弃最早未处理的任务请求
线程池的线程分配流程
图片: https://uploader.shimo.im/f/sIyByKFIjtvWP7eu.png提交任务后,如果线程数量小于corePoolSize,无论当前线程池是否有空闲的线程,都会创建新线程
当创建的线程数等于corePoolSize时,提交任务会被加入到设置的阻塞队列中
当队列满了,则会创建非核心线程执行任务,直到线程池中的线程数量等于maximumPoolSize
当线程数量等于maximumPoolSize时,新提交的任务无法加入到等到队列,也无法创建非核心线程直接执行,此时执行拒绝策略。

15、一些线程安全的容器

vector、hashtable、Collections.synchrnsizedMap()、ConcurrentHashMap

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值