二、JUC

image.png

1、线程状态

new新建、runnable可运行、terminable终结、blocked阻塞、waiting等待、timed-waiting有时限等待

不可逆状态

当new状态的线程调用start()方法时,线程才会进入runnable状态,runnable状态代码执行完毕进入terminated态。如果其获取锁失败,则会进入blocked态,直到获取锁成功进入runnable态。

可逆状态

多线程争抢同一锁时,竞争失败的线程进入blocked状态,直至runnable态的线程释放锁后,其余线程重新争取锁,获取成功的线程转为runnable态。
runnable态的线程,等待某条件,调用wait()方法释放锁进入waiting态,当某条件调用noyify()后唤醒该线程,该线程重新争抢锁成功后进入runnable态。
runnable态的线程调用wait(long)(带等待时间参数的wait()方法)释放锁进入timed-waitng态,有其他线程调用notify()或等待时间到后,其被唤醒,争抢锁后转为runnable态。如果其调用sleep()方法,则不释放锁进入timed-waiting态,等到固定时长后重回runnable态。

Java中的runnable涵盖了就绪、运行和阻塞I/O

2、线程池中的核心参数

image.png
核心线程指任务执行完毕后,仍需保留在线程池当中的线程。
救急线程指执行完任务后,不保留在线程池中的线程,其生存时间受3keepAliveTime和4unit时间单位控制。
workQueue一般有上限制,对任务起到缓冲作用,当核心线程数目满时,新任务进入workQueue中,只有当workQueue中也满时,任务才会进入救急线程中。

1、corePoolSize核心线程数目

核心线程的最大数目,可为0。

2、maximumPoolSize最大线程数

核心线程+救急线程的数量。

3、keepAliveTime生存时间

针对救急队列。救急队列完成任务后的生存时间。

4、unit时间单位

针对救急对列。

6、threadFactory 线程工厂

可以为线程创建时取名。

7、handler拒绝策略

当核心线程、救急线程与workQueue中全满时,执行拒绝策略。
1、AbortPolicy(默认):抛出异常。
image.png
2、CallerRunsPolicy:由调用者(提交操作者)自己来运行该线程。
3、DiscardPolicy:直接丢弃任务不运行(无异常、无错误)。
4、DiscardOldestPolicy:把workQueue中等待最久的任务丢弃,将新进的任务加入workQueue中。

3、关键字

wait()和sleep()

image.png

lock和synchronized

image.png

volatile能否保证线程安全

image.png

可见性问题

短时间内多次从内存中读取某值 image.png image.png JIT为了提高效率,减少cpu和内存的频繁读取,直接将stop值默认为false

image.png
造成了可见性失效。
所以加volatile,JIT不会去优化该变量,可以解决该问题。

有序性

volatile使用内存屏障实现指令重排序,变量加上volatile会给读和写加上不同的内存屏障。
当volatile加在int y上时,对y的写的操作会加一个向上的屏障,阻止上面的代码排下来。
image.png
对y的读的操作会加一个向下的屏障,阻止下方的代码上去。
image.png
总结:对于两个变量,对加volatile的变量的写操作应放在下方,读操作应放在上方。

4、悲观锁和乐观锁

image.png

unsafe_cas(cas:compare and set比较并替换)

unsafe修改变量时,通过cas保证其原子性,通过不断重试的机制,且cas需要和volatile配合使用。

image.png

例如:int o= 10;如果有其他线程改变了o的值,则cas读取到的o与之前的值不同时,cas修改失败返回false。 image.png

悲观锁

主要是通过互斥的方式,阻止线程交错来阻止共享变量的线性安全问题。
例如用synchronized,t1,t1共享变量,t1线程拿到了lock,即使cpu时间轮到t2(处于monitor即blocked态),t2线程也会因为没有lock而无法修改变量。

乐观锁

不加锁,支持并行执行,主要通过cas查看值是否被修改,如果修改了,就重试获取最新值进行操作。
image.png

5、Hashtable和concurrentHashMap

https://www.bilibili.com/video/BV15b4y117RJ?p=84&t=1.4
image.png
HashTable
初始容量为11,每次扩容为扩前容量(2-1),其hashcode不需要二次hash,且整个表一把锁,只允许单线程操作。
*ConcurrentHashMap

jdk7

segment+数组+链表,每个segment一把锁,超过3/4扩容。
初始capacity为16(初始segment(并发度)也为16),每个segment的容量为capacity/segment(最小为2)。
其hashcode需要二次hash(因为容量为2的n次幂,分布不是很好),segment的索引为取二次hash的高n位(即segment/并发度为2的n次幂),小数为二次hash最低位

jdk8

类似HashMap,每个数组节点的头节点一把锁,扩容则是满3/4则扩容(区别超过)

6、ThreadLocal

https://www.bilibili.com/video/BV15b4y117RJ?p=96&t=375.9
image.png