阅读顺序:
https://blog.csdn.net/qq_21033663/article/details/79571506
1、Object
https://blog.csdn.net/chengzhang1989/article/details/78703818
1) wait(), notify(), notifyAll(), wait(timeout)
对象监视器,ObjectMonitor
原来所谓的对象监视器就存储在对象的头部mark_word中!!!
锁正处在膨胀中,mark_word值为0x00000000时表示锁正在膨胀中
wait:
综上,我们来总结下Object.wait()实现原理:
a,首先能够调用wait方法,说明已经持有锁(或者说进入了synchronized代码块),而wait依赖于对象监视器objectmonitor的实现,所以首先得要获取这个monitor
b,monitor的指针存放在对象的头部,如果对象的头部没有,首先尝试从当前线程维护的omFreelist链表中分配,如果没有,从全局的gFreelist中分配,如果还没分配到,通过new的方式分配一个128大小的ObjectMonitor数组,放入到当前线程的omFreelist,以及全局的gFreelist,同时设置必要的信息
c,monitor分配好以后,进入wait,首先将当前线程封装成一个ObjectWaiter放入到objectMonitor的waitset队列中,然后退出对象监视器,挂起当前线程!
notify
notify时只是把notify标志职位1,当前线程加入到enter(synchronized)队列中,准备重新获取锁。既没有unpark挂起的线程,也没有调用ObjectMonitor::exit方法释放锁!!!!!
主要区别就是wait放入waitset然后会park;
notify是放入enter但是没有调用park
2) hashCode(), equals()
3) clone()
深拷贝,浅拷贝
2、String
1) char[] value
2) int hash
3) equals(), startWith(), endWith(), replace
3、AbstractStringBuilder
1) char[] value
2) int count
3) 扩容:翻倍,内存不够则取所需最小。 将扩大的部分全部用’\0’
扩容或add前先确认长度,后变长
int newCapacity = (value.length + 1) * 2;扩容
char数组
append 先扩容
利用System.arraycopy
insert在某个位置插入str:System.arraycopy(value, index, value, index + len, count - index);
4、StringBuffer
1) 继承AbstractStringBuilder
2) synchronized方法保证线程安全
3) char[] toStringCache
5、StringBuilder 继承AbstractStringBuilder
13.Thread
https://blog.csdn.net/caoxiaohong1005/article/details/80312396
JVM退出时Daemon线程中的finally块中的代码不一定会执行。因此不能依靠finally块中的内容来确保执行关闭或清理资源的逻
https://blog.csdn.net/linxdcn/article/details/72819817?locationNum=13&fps=1
四个状态
BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify,
流程、主要方法、总结
start()
// 启动一个线程 public synchronized void start() { // 线程不能重复start if (threadStatus != 0)
interrupt()
同步锁,标志位
synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { // 只是设置了中断标志位
join()
调用wai(0)
不用stop
https://www.cnblogs.com/DreamDrive/p/5623804.html
ThreadLocal
https://www.cnblogs.com/dolphin0520/p/3920407.html
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
没有先set,直接get的话,运行时会报空指针异常。
但是如果改成下面这段代码,即重写了initialValue方法:
创建线程:
java中创建线程的三种方法以及区别
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用三种方式来创建线程,如下所示:
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2、java.util
6、ArrayList
1) Object[] elementData
2) int size
3) 默认大小10
4) 扩容:翻倍,不够取所需最小
但ArrayList非线程安全,达到数组长度时每次扩大50%:用>>1的方式来加原来的长度
所以>>1等于乘以0.5
add时限保证容量,检查好容量后,add对象进去
https://www.cnblogs.com/woshimrf/p/arraylist.html
7、LinkedList
1) Node {E item, Node prev, Node next}
2) int size
3) Node first
4) Node last
5) linkFirst(), linkLast(), linkBefore(), unLinkFirst(), unLinkLast(), unLink(), indexOf()
8、HashMap
key可以为null
1) Node{int hash, K key, V value, Node next}
2) 默认容量16,负载因子0.75f
3) int size, modCount, threshold, float loadFactor
4) Node[] table
5) Set entrySet
6) put():根据key算hash,根据容量和hash算index,table[index]没有直接添加到数组中,table[index]有,若index位置同一个key则更新,否则遍历next是否有,有则更新,无则新增,最后根据thread与size判断是否扩容。注:扩容时容量翻倍,重新算hash复制到新数组
7)get()类似
注:先比较hash,若相等在比较equals
9、Hashtable
1) 结构实现与HashMap基本一致
2)通过synchronized方法保证线程安全
10、HashSet:委托给HashMap,其Value是同一个默认对象
11、LinkedHashMap继承HashMap
1) Entry{HashMap.Node, Entry before, after}
2) Entry head, tail
3) 重写newNode()添加节点时,除像HashMap中添加外,保存before、after信息
12、LinkedHashSet继承HashSet:不知道如何实现的顺序?
13、AbstractMap维护EntrySet,AbstractSet维护Iterator,AbstractList维护Iterator
14、ConcurrentHashMap
1) JDK1.7及以前:
a、Segment[] ,HashEntry[] , HashEntry{hash, k, v, next}
b、根据key算hash,根据hash和Segment的大小算位置,每个segment拥有一个自己的HashEntry[]
c、get():不加锁,volatile类型
d、put(): 对相应segment加锁
e、size():各HashEntry[] 之和,先不加锁算两遍,若一致则返回,若不一致则加锁重新计算
2)JDK1.8
a、Node{hash, key, value, next}
b、Node[] table
c、大多数操作类似于HashMap,不同CAS方式设置,根据key算hash,在根据hash和容量算index,对table[index]加锁,从而达到更大的并发量
d、get(): 同HashMap
e、put(): 对table[index]加锁
15、TreeMap
1)红黑树,即自平衡二叉查找树,时间复杂度O(logn)
2)Entry{K k, V v, Entry parent, left, right, boolean color}
3)Entry root,int size, int modeCount
16、TreeSet:委托TreeMap实现
18) Collections 3
https://www.jianshu.com/p/51ce612db017
binarySearch方法
该方法会根据条件的不同调用两个私有的方法来进行查找。如果List支持随机访问,并且小于二分查找的阈值5000,则调用indexedBinarySearch,否则,调用iteratorBinarySearch,借助迭代器来进行访问。
shuffle方法
shuffle翻译过来是重新洗牌的意思,该方法是将list原有数据打乱生成一个新的乱序列表。通俗点来说,旧相当于重新洗牌,打乱原来的顺序。还有一点,shuffle方法再生成乱序列表的时候,所有元素发生交换的可能性是近似相等的。
19) Arrays 3
26) SubList 4
7、java.util.concurrent.locks
1) Lock 2
公平锁和非公平锁通过new ReentrantLock(true or fals)决定
https://blog.csdn.net/future234/article/details/80576623
公平与非公平的lock方法区别:先尝试用乐观锁update来插队
1、非公平
//非公平锁if里面的条件就是插队,
//compareAndSetState(0, 1)
//意思就是插队看能不能拿到锁 //能拿到把当前线程设为sync的独占线程
否则就是正常acquire获得锁
2、公平
公平锁只能正常获得锁
获得锁流程:
1、公平
若锁是否空闲(打菜阿姨是否空闲)
hasQueuedPredecessors表示是否在队首
update乐观锁、设置当前线程
若当前线程重入锁返回true,状态加一
2、非公平
获得锁流程会再次尝试插队。
获取不到锁,就addWaiter放入队列:入队是循环,先创建队列,后入最后
acquireQueued循环获取锁:判断当前这个节点是不是头节点
释放锁:状态置位0,然后当前线程置位null
2) Condition 2
3) ReentrantLock 2
https://blog.csdn.net/mayongzhan_csdn/article/details/79374996
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
要点0的答案:AQS的volatile int state +1表示获取到了锁。unsafe返回true就是能获取
要点1的答案
a: 保证变量在线程之间的可见性 就是上面说的
b:禁止指令重排序 在编译阶段插入内存屏障,来特定禁止指令重排序
update时如乐观锁volatile
重入机制:
在tryaquiry方法中
先判断状态0
后判断是否为当前线程
两个循环
一个循环入队:判断队头和创建队,第二个循环就入队
一个是循环唤醒:判断是否可用,当前的node是否已经在队头
4) ReentrantReadWriteLock 2
https://blog.csdn.net/mayongzhan_csdn/article/details/80731935