无锁,偏向锁,轻量级锁,重量级锁
自旋锁,
互斥锁,读写锁,
公平锁,非公平锁,
线程池,
AQS,
本地线程,
死锁,
15.生产者消费者,
16.原子操作Atomic,
17.各种关键字
18.实现线程的四种方法
19.future啥的
20.线程安全的HashMap
21.阻塞队列
22.其他
23.实操
24.StampedLock
25.CompletionService(future,有返回值的线程池) https://blog.csdn.net/u010185262/article/details/56017175
26,钩子线程Runtime.getRuntime().addShutdownHook(new Thread())
https://blog.csdn.net/sinat_26661453/article/details/80629947
0.sync锁
sync锁的是对象而不是代码块,Java对象头在内存中的布局分为三块区域,对象头,实例数据,对其填充。
其中对象头有make work区和类型指针,
Markwork用于存储对象自身和运行时数据,如哈希码hashcode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳,占内存大小和虚拟机位长一致。
类型指针,指向对象的类元数据,通过这个指针确定该对象属于哪个类实例。
另:线程创建栈帧的时候会创建一个锁记录表,标志区的线程指向锁记录表
1.无锁
2.偏向锁
由于发现大多数不存在锁竞争,引入偏向锁来提升效率,不需要进行竞争操作,通过判断标志位来进行加解锁,偏向锁只进行加锁操作,只有锁竞争的时候才解锁或者当到达安全点的时候解锁,到达安全点的时候,先暂停持有锁,然后判断线程是否活着,没活更改为无锁,活着查锁记录表,锁升级,最后唤醒线程。
3.轻量级锁
当线程判断标志位对象不属于当前线程时,进行CAS获取,获取失败,偏向锁升级为轻量级锁(更改标志位)有锁竞争升级为重量锁,锁阻塞,解锁,通过CAS解锁,把锁记录表替换回对象头,如果失败(竞争锁已经更改),释放锁(标志位重置0)唤醒线程,进行重新争夺锁。
4。重量锁
不进行自旋操作,直接阻塞唤醒
5,优缺点
偏向锁,速度快,适合一个锁的情况下,缺点当有锁竞争会设计锁撤销操作。
轻量级锁,线程不会阻塞,提高锁效率,适合追求响应时间,同步代码块,缺点自旋,耗费CPU
重量级锁,适合锁竞争,耗费时间长,追求吞吐量,不耗费CPU,
6.lock锁
7.阻塞锁/可重入锁
线程进行CAS更改状态,其他的进入队列等待唤醒,会先判断是否是当前线程,是的话通过数量加一,实现可重入。
8读写锁,
读锁先判断是否有写锁的存在,有并且不是写锁的线程添加入队列(双向后插),是写锁可以直接获取到,获取到锁之后计数加一,解锁计数减一,看看队列是否有待运行的运行,
写锁先判断是否有线程运行,有进入队列等待,没有运行
解锁,唤醒队列第一个,然后第一个在唤醒后一个,唤醒那个在唤醒后一个递归,执行原来的计数流程,如果不是当前线程,通过本地线程存储,唤醒判断,如果下一个等于null或者下一个不是写锁(写锁nextWaiter为null,读锁不是)唤醒线程,是停止唤醒队列。
解锁,计数减一,判断是否还有写锁,有不操作结束,没有取队列唤醒。
8.1锁降级指当前线程把持住写锁再获取到读锁,随后释放先前拥有的写锁的过程。
为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此时有另一个线程T获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新,如果当前线程获取读锁,则线程T会被阻塞,直到当前线程使用数据并施放读锁之后,线程T才能获取写锁进行数据更新。
9,公平锁非公平锁
公平锁每次都直接加入队列里面排队等待运行,非公平锁,先尝试获取,获取不到在添加队列。
10,线程池,ThreadPoolExecutor
ThreadPoolExecutor(核心线程数,线程池大小,非核心线程超时回收时间,时间单位,任务队列,线程工厂,超过线程池大小处理方法);
线程池,生产者消费者模式,核心线程在运行结束之后循环调用getTask()方法,如果队列里有带运行的取出执行,如果没有核心线程await()休眠,当有新线程进来的时候唤醒核心线程,调用getTask方法时取出执行。如果队列满了,但是没有超过线程数,那么线程池会调用线程工厂在创建一个线程去执行。
线程池监控,可以继承线程池,重写beforeExecute,afterExecute方法。
11,AQS(AbstractQueuedSynchronizer)
AQS抽象队列同步器,线程锁的核心实现,共享锁,独占锁,通过标识区分独占EXCLUSIVE,共享SHARED,例子读写锁,读共享,写独占,共享锁会释放队列里面的下一个排队的线程。
基于aqs的实现,闭锁,栅栏,信号旗。
11.1闭锁。CountDownLatch
创建闭锁,指定锁数量,然后await,其他线程通过countDownLatch.countDown();减锁数量,当数量为0时唤醒await的线程运行。
11.2栅栏。CyclicBarrier
创建栅栏,指定数量,其他线程调用cyclicBarrier.await();计数减一,当计数为0时,执行栅栏指定的线程,然后在唤醒其他线程运行,重新赋值回设定的数量,可重用
11.3信号旗Semaphore
创建信号旗,指定数量,semaphore.acquire();申请一个,semaphore.release();归还一个,当申请的数量大于指定的线程休眠(共享锁模式),当归还后唤醒队列里的
12本地线程ThreadLocal
本地线程实际是map,初始为16,扩容触发为1/2(16*2/3 * 3/4)时,扩容大小为2倍,key冲突的处理是开放定址法,往后找null的没有在从头来,因为用完即删,冲突少。
13LockSupport
LockSupport.park();阻塞线程会先判断状态然后阻止,unpark直接更改状态运行,先unpark在park直接运行因为park先判断状态,可以多次unpark但不能多次park
14死锁
14.1对象锁互相等待
14.2两次LockSupport.park()
14.3读写锁,先读在写
14.4没有解锁
14.5t1线程wait,t2线程需要等待t1线程时死锁,t1wait,然后外边t1.join()。
https://blog.csdn.net/jyy305/article/details/70077042
15生产者消费者
15.1通过阻塞队列实现
15.2通过wait(),singleAll()实现
16.原子操作Atomic,
17.各种关键字
17.1 (https://www.jianshu.com/p/f4454164c017)wait,notify,必须在sync下,原因是因为,wait,notify必须要获得线程的monitor(监控),monitor采用ObjectMonitor实现,ObjectMonitor有两个队列,一个是_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程。
wait方法,解除所有限制,进入到waitSet区,其他线程开始竞争,notify方法,获取waitSet第一个线程唤醒,notifyAll循环唤醒,根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过Atomic::cmpxchg_ptr指令进行自旋操作cxq,
notify和notifyAll并不会释放所占有的ObjectMonitor对象,其实真正释放ObjectMonitor对象的时间点是在执行monitorexit指令,一旦释放ObjectMonitor对象了,entry set中ObjectWaiter节点所保存的线程就可以开始竞争ObjectMonitor对象进行加锁操作了。
17.2 线程对象的wait()方法运行后,可以不用其notify()方法退出,会在线程结束后,自动退出。
2 线程间的等待唤醒机制,最好不要用线程对象做同步锁!
线程正常结束后,会使以这个线程对象运行的wait()等待,退出等待状态!而如果在运行wait()之前,线程已经结束了,则这个wait就没有程序唤醒了。
原码里的join()方法,实际上就是运行的 wait(). 需要运行join的线程运行join方法,实际上是在此线程上调用了需要加入的线程对象的wait()方法,加入的线程运行完后,自然从wait退出了。
17.3join
底层调用while(isAlina())wait(0);wait停止的是主线程,判断的是调用join方法的线程,主线程M调用线程A的join方法,判断A是否存活,活着运行wait(0)方法,当A线程死去,wait自动唤醒,然后判断A线程已死,然后继续运行以下代码。而对于时间的参数timeout需要注意的是,如果输入0不代表不暂停,而是需要特殊情况自己苏醒或者notify唤醒,这里有个特殊点,wait(0)是可以自己苏醒的。
22,其他
创建线程,Windows下占用1M空间,Linux下占用10M空间,
cpu密集就是算的多,io密集就是读写多,CPU密集型任务,线程池size应为CPU数+1; IO密集型任务,线程池size应为CPU数/(1-阻塞系数)
各种锁:https://blog.csdn.net/qq_35119422/article/details/81488320
线程池:https://www.cnblogs.com/studyLog-share/p/5286290.html
线程池策略:https://www.cnblogs.com/studyLog-share/p/5286290.html
停止线程池:http://justsee.iteye.com/blog/999189
1.1、线程之间如何通信?
通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
a) 在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。(重点)
b) 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。
1.2、线程之间如何同步?
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
在共享内存的并发模型里,同步是显示进行的。因为程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
23,实操
使用jstack分析线程状态https://www.jianshu.com/p/6690f7e92f27
24,StampedLock (读写锁升级版)
https://blog.csdn.net/fouy_yun/article/details/77824969
https://blog.csdn.net/huzhiqiangcsdn/article/details/76694836
https://blog.csdn.net/lc0817/article/details/51878807
25,Disruptor
数组当做一个环,然后生产者消费者由一个调配者控制,生产者消费者通过cas来获取需要生产和消费的槽点,多生产者的时候,分配的槽点之后,只有等前一个位置的提交之后才能提交,消费者只能消费最大的提交槽点之前的值,数组避免了伪共享的问题,(伪共享:https://www.cnblogs.com/cyfonly/p/5800758.html)
26ForkJoinPool
https://blog.csdn.net/Holmofy/article/details/82714665
