arrayList:
arratlist 内部使用了一个obj类型的数组,初识容量为10;
arraylist线程不安全的原因是,arraylist添加元素的过程并不具备原子性
arraylist可以无限的添加元素,因为arraylist 每次添加元素之前都会跟list的容量相比较,若没有指定初始容量,则扩容时, 按照原容量10,来进行扩容
如果容量小了就会发生扩容 扩容大小为原数组的大小加上原数组长度的一半。(若原来的数组大小,小于添加一个元素之后的size,就发生扩容)
如果扩容之后还不够,那么容量就是这次添加元素之后容器的size长度
扩容之后,旧数组指向新的数组,使用了arrays.copyof()方法来复制了原数组中的内容
arraylist数组删除某个索引位置处的数据耗时是因为,需要使用system.arraycopy方法来移动整个数组
Fail-Fast机制是根据内部的modcount变量来验证的。用来保护数据的安全行
String:
String不可变的原因是,内部使用了char[]数组来构建String字符串,每次new 一个新的字符串的时候,都会将内部的char[]数组来指向新的字符串。
StringBuilder:
StringBuilder内部也使用了数组来表示String字符串,不同的是,StringBuilder支持动态扩容,初始容量为16,若当前字符数组的长度 < append()之后的长度,那么就扩容,扩容大小为原容量的大小 * 2 + 2。使用system.ArrayCopy()来完成数组的复制工作。
StringBuffer:
与StringBuilder相同,不过该版本是线程安全的。全部使用synchronized来保证线程安全。
integer:
内部有一个静态内部类IntegerCache,静态内部类内部使用integer[]数组,用来缓存 -127 - 128之间的数,使用静态代码块,来在类加载的时候,完成初始化工作
Integer s = 3; 会发生自动装箱的事情,也就是说s会被包装成integer类型,并且 3是数组中已经缓存好的数值
直接通过new关键子创建出来的integer对象是不同的对象,因为在堆中分配了不同的内存地址
integer s = 3; s == 3? 包装类型和基本类型之间的比较,比较的数字部分。其他的包装类与其相同,内部都使用了缓存
hashmap (jdk1.7):
hashmap实现map<k,v>接口
默认容量16,默认装载因子是0.75,最大容量是2^30
内部有一个静态内部类entry,表示一个mapping映射
hashmap使用entry数组来接收k,v键值对,数组的默认大小是16
(hashmap初始化问题)hashmap初始化的时候,若没有给容量,那么默认threadhold为16,put时,若hashmap为空,那么会初始化容量,若threadhold大于1,那么就是(threshold - 1) << 1,否则就是1
(扩容问题)之后确定threadhold的值 = hashmap的容量 * 加载因子(loadFactory 0.75f),若hashmap的size > threhold
那么hashmap会发生扩容,容量为原来容量的两倍,并且会遍历原来的hashmap,从新hash(key),将旧的值复制到新的hashmap中新的位置(每个entry下的链表还是在同一个链表下),是一个比较费时的操作,应该尽量避免扩容。
(加载因子的问题)不能太大,也不能太小,太大的话hashmap中的元素太多,更容易发生hash冲突,太小的话元素稀疏,浪费空间。
(确定entry节点位置的问题)使用 hashmap.lemgth - 1 & hash(key) 使得元素分布的更加均匀
(key为null的情况)若put的k,为null 那么在table[0]的位置上查找k为null的entry节点,若找到,那么覆盖原来的value,返回旧值
若没有找到,那么在table表中找到对应的位置,放入,若该节点的K为null那么k的hash值为0,且放在table【0】的位置上
(key不是null的情况)若k不是null,算出key的hash值,根据hash值,找到table表中对应的位置,若该表中存在元素,且key的hash值,key值;
都等于现在put的元素,那么用现在的value值覆盖原来的旧value值,且返回旧value值
(hash冲突)若key的hash值相同,但是key不相同的情况下,那么就发生了hash冲突,hashmap会将现在最新的entry节点放入对应的table中,将旧的ertry节点,作为现在节点的next节点。
(hashmap hashtable的区别)hashtable是线程安全的,使用了synchronized关键字来保证线程安全,而hashmap是线程不安全的。
hashtable不允许key为null ,而hashmap可以
hashmap中定位entry元素的位置使用hash(key) & hashmap.length - 1.而 hashtable是取模运算。
(使得hashmap线程安全的方法) 使用Collections.synchronizedMap(map),该方法将map修改为final域对象,并且在对hashmap中元素的读写时,增加了synchronized关键字来保证线程安全;
accseeOrder属性,默认为false,表示按照插入顺序来访问entry对象,true时表示按照访问顺序来访问对象
(get方法解读)若key为null,若hashmap的size为0,那么返回返回null,否则在在table【0】的链表中匹配key为null的entry
若匹配到,那么返回value,否则返回null。
若key不是null,那么就根据hash(key) & hashmap.length算出该entry在table中位置,若hash(key), key都相同那么返回对应的entry,否则返回null
之后返回entry的value值。
LinkedHashMap与hashmap相同,区别就是LinkedHashMap使用了双向链表,更加利于增加删除节点
lock :
(排他锁,公平锁):获取锁的过程
首先lock的实现依赖于AbstractQueuesynchronizer(队列同步器)
同步器中有一个静态内部类Node表示当前线程获取锁的状态,以及当前线程的前驱,后继,以及下一个等待的节点
获取独占锁的过程(公平):
获取锁的时候会先判断此时锁是否被占有,在lock中使用state变量来表示,0表示没有占有,1表示占有
若此时没有被占有,判断aqs队列中是否有节点,若没有节点,尝试使用CAS算法来更新state状态,若更新成功,表示获取锁,返回true,否则表示获取锁失败,返回false;
若当前线程是重入获取锁,将state状态设置为+1,返回true,获取锁成功。
若获取锁失败,将该线程包装为节点放入aqs中。
放入队列中尝试获取锁,若当前节点的前驱节点是头节点,那么可以尝试获取锁,若CAS成功,表示获取锁,将该节点设置为头节点。若获取失败,则将当前线程挂起,返回中断状态,以死循环的方式不断的来尝试获取锁。
(排他锁,非公平):
过程如上,但是此时线程获取锁,不需要判断aqs中是否有节点,直接使用CAS来更新状态,若成功,那么获取锁,否则加入到aqs对列中,以死循环的方法来不断的尝试。
释放的过程:
因为锁是重入锁,所以直到state为0时才真正释放锁。释放锁之后,将该线程的等待状态置为0,唤醒下一个阻塞的线程
写锁的获取与释放 :
过程与上述相同。
semaphore(java并发工具,用以支持限制线程的并发数):
使用方法:semaphore.acquire()获取锁 semaphore.release(); 释放锁
实现原理:主要依靠CAS算法,和一个数值状态来完成,数值状态表示可以同时运行的线程数,每当有线程尝试更改数值状态时:若数值状态小于0表示此时不再支持并发,将该线程加入对列以死循环的方式来不断的获取锁。否则表示可以同时并发
CyclicBarrier(java并发工具,用来支持,在某一时刻,所有准备就绪的线程同时运行)
使用方法:cyclicBarrier.await();
实现原理:内部使用了lock排他锁,使用排他锁来互斥的修改内部变量count,若count != 0 那么当前线程将使用condition.await()方法来被阻塞,若count == 0,那么将调用singal()方法来唤醒阻塞再次的线程
CountDownLatch(java并发工具,用以支持,线程等待某个最终信号出现之后在运行):
使用方法:countDownLatch.await(); 线程等待某个信号 countDownLatch.countDown(); 修改信号,来达到最终信号
实现原理:与共享锁实现原理相同