1.HashMap解决哈希冲突。链表法,红黑树和链表switchkey可以允许为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-12,2的n次方,所以可以保证(n-1)&length,可以保证和hash%array.lengthmodulo3一样的效果。展开:2倍容量扩容,jdk8不会像jdk7那样完全hash一次,jdk8扩容只能在原来的索引处进行,或者jdk1.7在索引+长度处扩容的死循环是header插入方式造成的,对于例子:k1->k2->k3,线程1和线程2需要同时扩充,线程1一下子做完,k3->k2->k1,线程2唤醒,k1->k2(之前),成环jdk1.8尾部插入方法2、ConcurrentHashMapjdk1.7ConcurrentHashMap由Segment数组结构和HashEntry数组组成。Segment是一个可重入锁,是一种数据和链表的结构。一个Segment包含一个HashEntry数组,每个HashEntry都是一个链表结构。ConcurrentHashMap的扩展只与每个Segment元素中的HashEntry数组的长度有关,但需要扩容时,只能扩容当前段中的HashEntry数组。也就是说ConcurrentHashMap中Segment[]数组的长度在初始化的时候就确定了,后续的扩容不会改变这个长度。所以对于jdk1.7来说,锁的并发是无法扩展的。jdk1.8取消了Segment数组,直接用table保存数据,锁的粒度更小,并发控制使用synchronized+CAS来操作(如果Node[]标签没有数据,通过CAS设置数据),如果有数据要insertorupdate,加上synchronized操作3.解决并发问题的方法有哪些?无锁:局部变量(在每个线程的工作内存中)、不可变对象、ThreadLocal(每个线程一个Map,Mapkey是当前实例对象,value是自己设置的值)、CAS(内存addressV,oldvalueExpectedvalueA,valuetomodifiedB),当V==A时,V可以修改为B,Java中的实现通常指的是一系列以Atomic为前缀的类,都使用CAS来存在一个Unsafe实例,Unsafe类体使用硬件级原子操作,问题:ABA(AtomicStampedReference记录版),周期长开销大,只有一个共享变量原子操作(AtomicReference)可以保证有锁:Synchronized和ReentrantLock都采用了悲观锁策略。Synchronized是在语言层面实现的,ReentrantLock是通过编程实现的。4、共享数据操作如果一个线程在读,另一个在写,会出现类似下面的操作问题:TaskInstancetaskInstance=taskInstanceCache.get(taskInstanceId);taskInstance.setState(ExecutionStatus.of(status));taskInstance.setEndTime(endTime);怎么做?只需使用taskInstanceCache.put(taskInstance);因为这个操作是原子性的问题,如果是那种动态注册是低频的,可以使用CopyOnWrite方式,基于DriverManager做一个例子1.使用SPI定义Driver接口,每个driver实现Drvier接口,比如com.mysql.jdbc.Driver,每个driverjar只有以java.sql.Driver为文件名的META-INF/services,value是要自动实现的驱动,可以在启动时加载驱动.一旦驱动程序被实例化,它就会发送给ServiceLoaderloadedDrivers=ServiceLoader.load(Driver.class);registeredDriversjava.sql.DriverManager中的CopyOnWriteArrayList来注册驱动2.DriverManager.getConnection()时,会遍历所有的驱动,看是否匹配。其实就是各个driver判断中的url6.ThreadLocal线程和虚拟机栈都在Java虚拟机栈空间中。每个线程都有自己的栈空间,相互独立。同一个方法可以每次调用不同的参数,线程之间互不影响。每次执行方法时,变量都存储在它自己的堆栈空间中。没有共享,就不会有线程安全问题从方法调用到方法返回,一个栈帧进栈出栈。栈的进程,栈帧包括局部变量、操作数栈、动态链接、方法返回地址等信息。ThreadLocal其实很简单,就是线程中有一个Map。Map的键是弱引用。输入值,所以设置它的值就是在当前线程的Map中设置一个值,获取就是获取当前线程的Map,然后通过ThreadLocal实例键Key1获取到你想要的值。为什么key是弱引用?其实很简单。从逻辑上讲,ThreadLocal的生命周期应该和Thread的生命周期是一样的。这样,Thread的生命周期结束后,面临销毁时,Thread中的ThreadLocal.ThreadLocalMap中的数据也会被回收,但是如果是线程池呢?线程池中的线程会被回收到线程池中,并不会被销毁,也就是说Thread仍然对ThreadLocal.ThreadLocalMap有强引用。因为线程没有被销毁,ThreadLocal.ThreadLocalMap还是不能被销毁,所以这就尴尬了,那我就不销毁了吗?2.所以存在弱引用。弱引用简单易懂。GC之后,如果只有弱引用存在,那我就杀掉你的ThreadLocal.ThreadLocalMap,但是如果强引用引用了我,好吧,还是要保留的,所以有场景。对于这种静态变量,privatestaticThreadLocalthreadLocal这种情况下是不会被回收的,比如单例。可以从线程1访问到线程n,然后设置值,这样从线程1到线程n就会在ThreadLocal.ThreadLocalMap中引用ThreadLocal.ThreadLocalMap中的threadLocal。什么时候销毁?3、对于这种静态变量,除非类被销毁,否则类本身对threadLocal有强引用,所以即使线程对它有弱引用,也不能被销毁。那我们该怎么办呢?线程池里的线程已经跑完了,而threadLocal还在我的线程里,不合理吗?所以设计了ThreadLocal,建议手动去掉。如果不去除,可能会造成漏线,无法解决。但是对于那种,threadLocal中没有被外界引用的key会在Thread中回收ThreadLocal.ThreadLocalMap中的key,那么value呢?每次set或者get的时候,你会发现如果key为null而value存在,你必须销毁它。7、线程状态NEW(新线程)、RUNNABLE(启动)、WAITING(等待、LockSupoort.park)、TIMED_WAITING(等待(时间))、BLOCKED(同步、重入锁)、TERMINATED(结束)8、Deadlock死锁产生原因:互斥、占用等待、非抢占、循环等待互斥是不可避免的占用等待:我们可以一次全部申请资源不能被抢占:当一个线程占用了一些资源去申请其他资源时,如果申请不到获得后,在一定时间后可以主动释放占用的资源。循环等待:按顺序申请资源9、synchronized原理monitorenter和monitorexitMonitor锁池:例如说两个线程需要同时加锁和访问数据。需要一个一个上锁,解锁的放到EntryList中。,然后放入waitingpool,条件成熟的时候notify或者singal给你,让waitingpool的queue数据放入锁池,这里一定要用notifyAll,很简单,因为你没有'不知道找谁工作,让你去争取竞争锁。如果条件不成立,则继续进入等待池。如果只是随机通知,可能会造成死锁。Owner是哪个线程获得了锁分类:自旋锁:不是锁,而是一种机制或者策略,说白了就是在不能获得锁的时候进行自旋,而不是立即释放CPU时间片,因为CPU状态切换也很耗时。偏向锁:大多数情况下锁总是被同一个线程多次获取,不存在多线程竞争,所以有偏向锁轻量级锁:当一个偏向锁被另一个线程锁访问时,偏向锁会升级为轻量级锁,其他线程以自旋锁的形式获取,不会阻塞,CAS重量级锁:悲观锁10、AQS公平锁和非公平锁,默认为非公平锁核心组件:currentstate状态(锁数,可重入锁),exclusiveOwnerThread(锁定线程),tryAcquire1,没有锁,我直接获取锁,state=1,exclusiveOwnerThread设置为当前线程,或者我自己重新入锁,继续state+12,锁不成功,怎么办?生成一个Node.EXCLUSIVE,独占锁,addWaiter生成一个队列,Node列表队列,如果不为空,直接添加,如果为空,初始化一个队列,一个节点HEAD节点,什么都不保存,然后挂一个节点3.然后进入一个for无限循环,看是否是第一个节点。如果是第一个节点,继续尝试获取锁,其实就是判断addWaiter(Node.EXCLUSIVE)的前一个节点是否是头节点。节点,就尝试获取锁4.大家都很高兴获取到锁。如果获取不到锁,设置precursor节点为SINGAL,直接LockSupport.park为unlock1,state-1并设置exclusiveOwnerThread为null2,从后向前遍历,得到第一个节点为非Cancelled节点而不是头节点Unparkfairlock:唯一的区别是在获取锁的时候,如果有锁,就得去队列里看,除非自己能锁上,否则请排队,并且您不能跳入队列以获取锁。AQS有两个队列,竞争锁队列和条件队列,和Synchronized的逻辑是一样的。不同的是可以有多个条件队列,可以相互await和singal11,LinkedListBlockQueue&ArrayBlockingQueueArrayBlockingQueue,一个lock锁,两个ConditionnotEmpty和notFull,同时只有一个线程通过读写阻塞自己(等待别人唤醒)LinkedBlockingQueue阻塞队列取:如果为空,则不能取;使用takeLock,notEmptyConditionput:如果满了,就不能put,使用putLock,notFullCondition两个锁来阻塞自己和唤醒自己并唤醒别人12、可以中断wait(),sleep(),join()的方法,取,放,可以直接thread.interrupt13,ThreadPoolExecutorpublicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)corePoolSize:核心线程maximumPoolSize:最大线程(最大线程-核心线程=可超时线程)keepAliveTime:超时线程时间TimeUnit单位:timeoutThreadtimeunitworkQueue:常用LinkedBlockingQueue和SynchronousQueuethreadFactory:线程工厂,也就是创建线程的工厂,可以使用Executors.defaultThreadFactory()或者自定义handler:rejectionpolicyAbortPolicy:默认策略,直接抛出RejectedExecutionException异常,例如,提交线程处理过程中的异常,重试提交DiscardPolicy:直接扔掉,一般你不要这样玩提交任务的用户线程运行当前任务。说白了就是线程池提交不了任务。如果我要占用主线程等用户线程来执行任务,可能会影响主线程任务的执行。execute(newRunnable())在任务提交的时候1.创建一个比核心线程小的核心线程。2.创建一个比核心线程大的核心线程。3.当队列满时,创建一个非核心线程addWorker1。使用ReentrantLock加锁,保证创建线程的HashSetworker的线程安全。,2.工作er集成了AQS,同时实现了Runnable,说明Worker是一个可以加锁的Runnable线程。3.启动工作线程。Worker本身是Runnable,但是它包含一个线程Thread,将Runnable放到Thread中,然后启动WorkerWorker使用AQS的两个作用:shutdown期间,可以完成正在运行的任务,因为正在运行的任务获取了线程的锁worker,所以在shutdown的时候,无法获取到锁,然后就不会中断了。正在运行的任务运行完就可以释放了,因为之前已经设置了SHUTDOWN标志。Worker一旦启动,就会开始getTask的无限循环。如果任务被获取,它将被锁定。执行任务getTask非常关键:如果执行了shutdowngetTask1,则设置线程池当前状态为shutdown,并中断空闲的Worker。Workerrun方法中有两个地方可以响应中断。第一个是task.run()。自己提交的任务可以响应中断,比如sleep。第二位是getTask的workQueue.poll和workQueue.take也可以响应中断请求。在shutdown的情况下,task.run不会被打断,最多只会打断getTask的workQueue.poll和workQueue.take空闲线程2。如果当前线程状态为shutdown,并且workQueue.isEmpty()为空,则直接返回null,或者下面的非核心线程超时如果执行了shutdownnow,无论是正在执行的任务还是核心线程被阻塞或者非核心线程等待超时线程会被中断,一起退出SynchronousQueue。为CachedThreadPool创建的CachedThreadPool都是非核心线程。核心原则:offer时,如果poll中没有thread,则失败。如果非核心线程直接创建offer时,如果有线程轮询,会复用之前创建的非核心线程put和take,相对阻塞,零容忍14.FutureTasksubmit(newCallable()),callable被传递给带有构造参数的FutureTask。FutureTask有两个特性:Runnable和Future。对象封装了newCallable(),其他方法同理。submit和execute的区别在于execute是在Worker的run方法中直接调用task.run,而submit实际上是调用FutureTaskrun中的callable.run方法callable.run。如果有结果,如果执行正常完成,通过CAS设置当前FutureTaskNEW为COMPLETING,设置outcome=v(计算结果),调用finishCompletion将getpark所在线程unpark(是一个栈,并且栈中的线程都执行了)unpark)get时,如果完成了,直接返回值,如果没有完成,类似AQSpushthreadLockSupport.park15,无法创建新的nativethread(OOM),如果在JVM中溢出,会导致java.lang.OutOfMemoryError:JavaHeapspace,可以创建多少个线程由一个计算公式决定:可以创建的线程数=(最大执行内存-JVM分配的内存-操作系统保留的内存)/线程堆栈大小如果不显示,设置-Xss或-XX:ThreadStackSize参数,Linux64上默认ThreadStackSize为1024k,也就是1MB,表示分配给线程的内存越大JVM内存,逻辑上可以创建的线程越少。感兴趣的请点赞关注!