1、HashMap
解决hash抵触,链表法,红黑树和链表互相切换
key是能够容许为null的,在Node节点下标为0处
替换的原理 :
两个hash值必须要相当,而后判断 (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
jdk8之后的亮点:
1、hash抵触,应用高16和低16异或,使得hash数据分布更加平均,而后再与length-1相 & ,jdk1.7是间接获得hash & length -1
2、2的n次方,就能够保障说,(n - 1) & length,能够保障就是hash % 数组.length取模的一样的成果
3、扩容:2倍容量进行扩容,jdk8不会像jdk7那样齐全hash一遍,jdk8扩容结束只能在原来index处,或者index + length 处
jdk1.7扩容死循环
都是头插入方式惹的祸,比方 : k1 -> k2 -> k3,线程1和线程2同时要扩容,线程1一下子做完了,k3 -> k2 -> k1,线程2 苏醒过来,k1 -> k2(之前的),就造成环了
jdk1.8尾插入法
2、ConcurrentHashMap
jdk1.7 ConcurrentHashMap由Segment数组构造和HashEntry数组组成。Segment是一种可重入锁,是一种数据和链表的构造,一个Segment中蕴含一个
HashEntry数组,每个HashEntry又是一个链表构造
ConcurrentHashMap 的扩容是仅仅和每个Segment元素中HashEntry数组的长度无关,但须要扩容时,只扩容以后Segment中HashEntry数组即可。
也就是说ConcurrentHashMap中Segment[]数组的长度是在初始化的时候就确定了,前面扩容不会扭转这个长度
所以说,针对jdk1.7来说,锁的并发度是不能扩容的
jdk1.8 勾销了segment数组,间接用table保留数据,锁的粒度更小,并发管制应用 synchronized + CAS来操作(如果Node<K,V>[] tab上没有数据就通过CAS设置数据),如果有要进行数据插入或者更新,加synchronized操作
3、解决并发问题的办法有哪些?
无锁 :
局部变量(每个线程的工作内存中)、不可变对象、ThreadLocal(每个线程一个Map,Map key是以后实例对象,value是本人设置的值)、CAS(内存地址V,旧值预期值A,要批改的值B),当V==A的时候,V才能够批改为B,在Java中的实现则通常是指是以Atomic为前缀的一系列类,都采纳了CAS存在一个Unsafe实例,Unsafe类体用硬件级别的原子操作,问题:ABA(AtomicStampedReference记录版本)、循环工夫长开销大、只能保障一个共享变量的原子操作(AtomicReference)
有锁 :
Synchronized和ReentrantLock都是采纳了乐观锁的策略。Synchronized是通过语言层面来实现,ReentrantLock是通过编程形式实现
4、共享数据操作
如果一个线程在读取,一个线程在写,有相似如下操作就会有问题 :
TaskInstance taskInstance = taskInstanceCache.get(taskInstanceId);
taskInstance.setState(ExecutionStatus.of(status));
taskInstance.setEndTime(endTime);
怎么办呢?
间接taskInstanceCache.put(taskInstance);即可,因为这操作是原子性的
5、CopyOnWriteArrayList应用场景
读多写少的场景,写的时候就copy一份数据,镜像供读取,明确ArrayList是有线程平安问题的,如果那种动静注册,低频的,能够应用CopyOnWrite模式
依据DriverManager来做实例
1、应用了SPI 定义了Driver接口,各个驱动来实现 Drvier接口,比如说 com.mysql.jdbc.Driver,每个驱动jar都有META-INF/services只有以java.sql.Driver为文件名,value是主动要实现的驱动,就能够在启动的时候加载驱动了,一旦实例化驱动就会向
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
java.sql.DriverManager 中的 registeredDrivers CopyOnWriteArrayList 进行驱动注册
2、在 DriverManager.getConnection() 的时候,会遍历所有驱动,看是不是合乎,其实就是各个驱动外面的url进行判断
6、ThreadLocal
线程和虚拟机栈
在Java虚拟机栈空间,每个线程都有本人栈空间,且互相独立,每次能够用不同的参数调用雷同的办法,且线程之间互不影响,每次执行办法的时候,变量都是存储在本人的栈空间,没有了共享,就不会呈现线程平安问题
栈帧是什么 : 是用于虚拟机执行办法调用和办法执行的数据结构,每个办法从调用到办法返回都对应着一个栈帧入栈和出栈的过程,栈帧包含局部变量、操作数栈、动静链接和办法返回地址等信息
ThreadLocal其实说白了很简略,就是线程外面有一个Map,Map的key是弱援用,ThreadLocal的实例,value是存入的值
所以要设置其值就是往以后线程的Map中设置一个值,获取就是获取以后线程的Map,而后再通过ThreadLocal的实例key获取你想要的value
重点
1、key为什么为弱援用?
其实很简略,按情理来说,ThreadLocal的生命周期应该是和Thread的生命周期是一样的,这样Thread生命周期走完了,面对销毁,同样Thread中ThreadLocal.ThreadLocalMap中的数据也会回收
然而如果是线程池呢?线程池中的线程会回收到线程池中,真正并不销毁,意味着Thread还是对ThreadLocal.ThreadLocalMap有强援用,因为线程不销毁,ThreadLocal.ThreadLocalMap还是销毁不了,所以这就很难堪,那我就不销毁了么?
2、所以就呈现了弱援用,弱援用其实说白了,简略了解,就是顺次GC过后,如果只有弱援用存在,那我就把你ThreadLocal.ThreadLocalMap干掉,然而如果强援用援用了我,好吧,还是须要保留的。所以就呈现了场景
对于这品种的动态变量,
private static ThreadLocal<String> threadLocal
这种状况下,是不会回收的,
比如说是单例。能够线程1到线程n进行拜访,而后设置值,这样线程1到线程n Thread中ThreadLocal.ThreadLocalMap 中都会有 threadLocal 的援用。什么时候销毁呢?
3、针对这种动态变量,除非类销毁,类自身对 threadLocal 有强援用,所以 thread 即便对其实弱援用,也销毁不了。那怎么办呢?线程池中的线程曾经运行结束了,threadLocal还在我线程中,不合理吧?
所以 ThreadLocal 设计了,倡议不必的,手动remove。如果不remove肯能会造成线程泄露,解决不了。然而针对那种,外界没有援用的 threadLocal中的key,会对 Thread中ThreadLocal.ThreadLocalMap 中的key进行回收,
value怎么办呢?每次set,get的时候会找如果key为null,value存在就要销毁
7、线程状态
NEW(new thread)、
RUNNABLE(start)、
WAITING(wait,LockSupoort.park)、
TIMED_WAITING(wait(time))、
BLOCKED(synchronized,reentrantlock)、T
TERMINATED(完结)
8、死锁
死锁产生的起因 :
互斥、占用且期待、不可抢占、循环期待
互斥是不能防止的
占用且期待 : 咱们能够一次性申请所有的资源
不可抢占 : 占用局部资源的线程申请其余资源时,如果申请不到,能够在肯定工夫后,被动开释它占用额资源
循环期待 : 依照程序申请资源
9、synchronized原理
monitorenter和monitorexit
Monitor
锁池 : 比如说两个线程同时要加锁拜访数据,须要一一加锁,未加锁的要放入到EntryList,其实就是一个队列
期待池 : 其实就是比如说条件不满足,是不是没有必要去获取锁去,那就放入到期待池中,条件成熟对你进行notify或者singal,让期待池队列数据放入到锁池
这里必须应用notifyAll,很简略,因为不晓得该让谁干活,都让你们去竞争锁去吧,如果条件不成立,持续进入期待池中,如果只是随机的notify一个,有可能会产生死锁
Owner 是哪个线程获取了锁
锁的分类 :
自旋锁 : 不是锁,是一种机制或者策略,说白了就是在获取不到锁的状况下,自旋一下,不立马开释CPU工夫片,因为CPU状态切换也很耗时
偏差锁 : 大多数状况下,锁总是由同一线程屡次获取,不存在多线程竞争,所以呈现了偏差锁
轻量级锁 : 偏差锁的时候,被另外的线程锁拜访,偏差锁就会降级为轻量级锁,其余线程会通过自旋的模式获取锁,不会阻塞,CAS
重量级锁 : 乐观锁
10、AQS
偏心锁和非偏心锁,默认是非偏心锁
外面外围组件 : 以后状态state(加锁数量,可重入锁)、exclusiveOwnerThread(加锁线程)、
tryAcquire
1、没有锁,我间接获取锁,state=1,exclusiveOwnerThread设置为以后线程,或者是我本人重入锁,持续state+1
2、没有加锁胜利,怎么办?生成一个 Node.EXCLUSIVE,排他锁,addWaiter 生成一个队列,Node列表队列,如果不为空,间接往后加,如果为空,初始化一个队列,一个节点HEAD节点,什么也不存,之后挂一个节点
3、之后进入一个for死循环,看是不是第一个节点啊,如果是第一个节点持续尝试获取锁,其实就是判断,addWaiter(Node.EXCLUSIVE)前一个节点是不是头节点,如果是头节点,就尝试获取锁
4、获取锁大快人心,获取不到锁呢,设置前驱节点为SINGAL,间接LockSupport.park住了
unlock
1、state - 1 并 exclusiveOwnerThread设置为null
2、从后往前遍历,获取第一节点是非Cancelled节点且不为head的节点unpark
偏心锁 : 惟一区别在于获取锁的时候,如果有锁,要去队列中看,除非是本人能力加锁,否则请排队,不能插队获取锁
AQS中有两个队列,竞争锁队列和条件队列,和Synchronized逻辑是一样的,不同的是条件队列能够是多个,能够互相await和singal
11、LinkedListBlockQueue & ArrayBlockingQueue
ArrayBlockingQueue
一个锁 lock
两个Condition notEmpty 和 notFull
同时只有一个线程进行读写
阻塞本人(期待他人唤醒)
LinkedBlockingQueue
阻塞队列
take : 如果为空,就不能take;应用 takeLock,notEmpty Condition
put : 如果满了,就不能put,应用putLock,notFull Condition
两把锁
阻塞本人、唤醒本人和唤醒别人
12、能够interrupt的办法
wait()、sleep()、join()、take、put,能够间接thread.interrupt
13、ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize : 外围线程
maximumPoolSize : 最大线程(最大线程-外围线程=能够超时的线程)
keepAliveTime : 超时线程工夫
TimeUnit unit : 超时线程工夫单位
workQueue : 罕用的 LinkedBlockingQueue和SynchronousQueue
threadFactory : 线程工厂,就是创立线程的工厂,能够应用 Executors.defaultThreadFactory() 或者 自定义
handler : 回绝策略
AbortPolicy : 默认策略,间接抛 RejectedExecutionException 异样,能够比如说在提交线程中进行异样解决,重试提交DiscardPolicy : 间接扔掉,个别不会这么玩DiscardOldestPolicy : 其实就是对头移除,队尾减少工作,个别也不必CallerRunsPolicy : 只有线程还没有敞开,那么这个策略就会间接在提交工作的用户线程运行当前任务,说白了,就是线程池提交不了工作了,我要占用用户线程比如说main线程来执行工作,可能会影响主线程工作的执行
execute(new Runnable()) 在工作提交的时候
1、小于外围线程创立外围线程
2、大于外围线程,向队列中压入
3、队列满,创立非核心线程
addWorker
1、应用 ReentrantLock 加锁,保障了创立线程的HashSet<Worker> workers
线程平安,
2、Worker是集成了AQS,同时实现了 Runnable,阐明Worker是一个能够加锁的Runnable线程
3、将Worker线程启动,Worker自身是Runnable,然而外面蕴含一个线程Thread,将Runnable放入到Thread,而后启动Worker
Worker应用AQS的两个作用:
在shutdown的时候,能够让正在运行的工作运行结束,因为正在运行工作是获取了这个worker的锁,所以shutdown的时候,去获取锁获取不到,而后就不interrupt
正在运行的工作能够运行结束之后推出,因为之前曾经设置了SHUTDOWN标记
一旦Worker启动之后,就开始有限循环的getTask,如果获取一个工作,则加锁,执行该工作
getTask很要害 :
如果执行了shutdown
getTask
1、设置线程池以后的状态为shutdown,将闲暇的Worker interrupt了,Worker run办法中有两处能够响应 interrupt
第一处就是 task.run(),自身提交的工作是能够响应interrupt的,比如说sleep,第二处是getTask的 workQueue.poll 和 workQueue.take 也能够响应中断请求
针对shutdown状况下,不会对 task.run进行 interrupt,最多只会对 getTask的 workQueue.poll 和 workQueue.take 闲暇线程进行interrupt
2、如果以后线程状态为shutdown状态,且workQueue.isEmpty()空了,那就间接return null, 或者上面走非核心线程超时
如果执行了shutdownnow
不论是正在执行的工作还是外围线程阻塞或者非核心线程期待超时的线程,都会进行interrupt,一起进行退出
SynchronousQueue,是针对CachedThreadPool的
CachedThreadPool创立进去的都是非核心线程,外围原理 :
当offer时候,如果poll没有线程在获取,那就会失败,间接创立非核心线程
如果在offer的时候,正好有线程在poll,那就会复用之前创立的非核心线程
put和take相对来说是阻塞的,零容忍
14、FutureTask
submit(new Callable()), callable 以结构参数传入到 FutureTask 中
FutureTask有 Runnable和Future两种个性,其实说白了就是创立了一个返回值的援用对象
submit其实比execute来说就多创立一个 FutureTask 对象,FutureTask对象封装了 new Callable(),而后其余形式都是一样的
submit和execute不同的是,execute在Worker run办法间接调用task.run,submit其实也是调用的 FutureTask run中的 callable.run办法
callable.run是有后果的,如果失常执行结束,通过CAS将以后FutureTask NEW 设置为 COMPLETING,设置outcome = v(计算结果),调用 finishCompletion
将通过get park住的线程进行unpark(是一个栈,栈中的线程都进行unpark)
get的时候,如果实现了间接将值返回,如果未实现,相似AQS 压栈 线程LockSupport.park
15、unable to create new native thread(OOM)
如果是JVM内溢出,则会使 java.lang.OutOfMemoryError : Java Heap space,能创立多少线程是由一个计算公式的 : 可创立的线程数 = (进行的最大内存 - JVM调配的内存 - 操作系统预留的内存) / 线程栈大小
如果不显示设置-Xss或-XX:ThreadStackSize参数的时候,Linux64 上 ThreadStackSize 的默认就是1024k,也就是1MB
就是说给JVM内存调配的内存越大,逻辑上能创立的线程数量越少
如感兴趣,点赞加关注哦!