Arraylist和LinkedList区别
1、Arraylist是基于索引的数据结构,所以查找的速度比较快,但是删除数据会很慢,因为删除中间的一个数据后,后面的所有数据都要往前移动一位。
2、LinkedList是基于链表的数据结构,LinkedList插入和删除元素比较快。但是LinkedList需要更多的内存,因为一个节点储存了两个字,自己和自己的下一个节点的位置。
与线程有关的集合类
线程安全的Vector&Hashtable
vector和hashmap都是粗暴的给所有有线程安全问题的方法加上synchronized锁。
线程不安全的ArrayList&HashMap
ArrayList和HashMap的方法都是不加锁的,因此线程不安全。
collections工具类里面的线程安全包装方法synchronizedlist&synchronizedmap。
SynchronizedList是Collections的一个静态内部类,但是并不能通过List list2 = new Collections().SynchronizedList(arrayList);这种方法建立SynchronizedList对象,因为Collections作为工具类,其构造方法是私有的,无法构造Collections的对象。
所以应该使用List list = Collections.synchronizedList(arrayList);来创建一个synchronizedlist的对象。
可以看到这个方法里面会判断list是否实现类RandomAccess接口,这个接口的作用是标记快速访问的类,像ArrayList便实现了这个接口,LinkedList接口没有实现。那么是如何实现线程安全的呢?
通过synchronizedList的构造方法中的super找到其父类为SynchronizedCollection,这个类中有个成员为mutex,源码中的注释为这个对象是用来当作锁的,默认的构造方法是将自己作为锁,另一个构造方法中有形参mutex作为锁。
synchronizedSet底层实现和上面基本一样,就不多赘述了。
Set set = Collections.synchronizedSet(new HashSet<>());
- 注意:SynchronizedCollection类中也有几个方法并没有加锁,是线程不安全的
// 迭代器方法
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
//可分割迭代器(splitable iterator),对于并行处理的能力大大增强
@Override
public Spliterator<E> spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream<E> stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream<E> parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
concurrent并发包下线程安全的类
ConcurrentHashMap
//ConcurrentHashMap的无参构造
public ConcurrentHashMap() {
}
/*有参构造,在这个构造方法中会根据initialCapacity进行计算,得到最小的大于initialCapacity的2的n次幂,例如32就对应着64。计算得到的这个数就是初始化的大小*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
//ConcurrentHashMap不允许添加键为空和值为空的对象
if (key == null || value == null) throw new NullPointerException();
//得到key的hash值,这个值是为正的,负数具有其他含义
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();// table为空,初始化table
// 得到key的hash得到对应的桶位置,当桶位置为空时,通过原子操作将这个node加入到桶位置
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 得到的桶位置不为空且此时hash==MOVED(-1),代表数组正在扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 得到的桶位置不为空且此时hash>0,这时要将node加入到链表或者红黑树中,
//此时会将这一个桶位置加锁,可以避免其他线程修改这个位置,但又不会妨碍其他桶位置的访问和修改
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 添加完node之后,如果链表的长度大于TREEIFY_THRESHOLD(8),
//会将链表扩展为红黑树,但此过程不一定执行,treeifyBin()方法中还会进一步//判断数组的长度,如果数组的长度小于64,便不会转换,而是将链表扩容。
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//private transient volatile long baseCount;
//private transient volatile CounterCell[] counterCells;
addCount(1L, binCount);
return null;
}
&CopyOnWriteArrayList&CopyOnWriteArraySet
HashMap的底层实现
数组+链表+红黑树
hashmap默认初始大小为16。负载因子为0.75(static final float DEFAULT_LOAD_FACTOR = 0.75f;)
负载因子的意思就是当数组中的元素个数>数组大小*负载因子时,要对数组进行扩容。
关于HashMap的put方法。
1、判断数组是否为空,为空则初始化数组。
2、数组不为空,根据key计算得到的hash寻找桶位置。如果桶位置为空,直接在当前位置创建一个桶,其中头节点为node。
3、若桶位置不为空(发生了hash冲突),遍历桶中是否有key和待加入的key相同,如果有,直接覆盖,没有的话就加入进去。
4、桶位置是链表就加到最后.
5、接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树。
6、桶位置是红黑树就加到红黑树中。
6、判断是否扩容。