二、JUC
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、线程池中的核心参数
核心线程指任务执行完毕后,仍需保留在线程池当中的线程。
救急线程指执行完任务后,不保留在线程池中的线程,其生存时间受3keepAliveTime和4unit时间单位控制。
workQueue一般有上限制,对任务起到缓冲作用,当核心线程数目满时,新任务进入workQueue中,只有当workQueue中也满时,任务才会进入救急线程中。
1、corePoolSize核心线程数目
2、maximumPoolSize最大线程数
3、keepAliveTime生存时间
4、unit时间单位
6、threadFactory 线程工厂
7、handler拒绝策略
当核心线程、救急线程与workQueue中全满时,执行拒绝策略。
1、AbortPolicy(默认):抛出异常。
2、CallerRunsPolicy:由调用者(提交操作者)自己来运行该线程。
3、DiscardPolicy:直接丢弃任务不运行(无异常、无错误)。
4、DiscardOldestPolicy:把workQueue中等待最久的任务丢弃,将新进的任务加入workQueue中。
3、关键字
wait()和sleep()
lock和synchronized
volatile能否保证线程安全
可见性问题
短时间内多次从内存中读取某值 JIT为了提高效率,减少cpu和内存的频繁读取,直接将stop值默认为false
造成了可见性失效。
所以加volatile,JIT不会去优化该变量,可以解决该问题。
有序性
volatile使用内存屏障实现指令重排序,变量加上volatile会给读和写加上不同的内存屏障。
当volatile加在int y上时,对y的写的操作会加一个向上的屏障,阻止上面的代码排下来。
对y的读的操作会加一个向下的屏障,阻止下方的代码上去。
总结:对于两个变量,对加volatile的变量的写操作应放在下方,读操作应放在上方。
4、悲观锁和乐观锁
unsafe_cas(cas:compare and set比较并替换)
unsafe修改变量时,通过cas保证其原子性,通过不断重试的机制,且cas需要和volatile配合使用。
例如:int o= 10;如果有其他线程改变了o的值,则cas读取到的o与之前的值不同时,cas修改失败返回false。
悲观锁
主要是通过互斥的方式,阻止线程交错来阻止共享变量的线性安全问题。
例如用synchronized,t1,t1共享变量,t1线程拿到了lock,即使cpu时间轮到t2(处于monitor即blocked态),t2线程也会因为没有lock而无法修改变量。
乐观锁
不加锁,支持并行执行,主要通过cas查看值是否被修改,如果修改了,就重试获取最新值进行操作。
5、Hashtable和concurrentHashMap
https://www.bilibili.com/video/BV15b4y117RJ?p=84&t=1.4
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则扩容(区别超过)