Vector 与 ArrayList的区别:
他们的原理都是通过数组实现的,增删慢,查询快。
Vector是线程安全的,ArrayList线程不安全,效率高。
我们去看源码:
Vector 的add方法是加了synchronized关键字,所以他是一个同步方法,线程是安全的,效率低,工作中即使要使用线程安全的List集合,也不使用Vector,而是用Collections工具类中的 Collections.synchronizedList(arrayList);他可以将线程不安全的ArrayList转换为线程安全的List。
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
ArrayList的add方法源码,没有使用任何多线程的锁,所以他是线程不安全的。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
HashMap 与 HashTable的区别:
还是看源码:
HashMap: 线程不安全,可以有null键,也可以有null值。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
HashTable的put方法被synchronized修饰,所以HashTable是线程安全的。从第一段源码可以看出不能有null值。 当然也不能有null键。
public synchronized V put(K key, V value) {
// Make sure the value is not null
// 这段源码可以看出,值不能为null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
HashTable的get方法,也是线程安全的。
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
通常情况下,也不会使用HashTable,因为在多线程的环境下,HashTable的get方法,只能让一个线程同时操作,所以他的效率根本就不高。所以在jdk1.5的时候就出现了 ConcurrentHashMap。
还有 java 提供了 Collectiions的类,有一个 Collections.synchronizedMap(hashMap); 他是可以将线程不安全的HashMap转换为线程安全的Map的集合。
接下来就说一下,为什么多线程不用HashTable,而使用ConcurrentHashMap。
原因就是HashTable虽然线程安全,但是效率低。而ConcurrentHashMap底层原理就是HashTable,只不过做了分段处理。
上面的源码也看见了,HashTable使用了Synchronized所以不管是put数据,还是get数据,都是加锁了的,所以多线程在操作map集合的时候,也是一条一条执行的,这样效率特别低。
在jdk1.5时,工程师就想了个办法,把这个ConcurrentHashMap集合,按照索引进行拆分。比如ConcurrentHashMap里有100条数据,就将这160条数据,按照索引拆分为16个HashTable,这16个HashTable就是16个不同的线程,专业名词叫:分段锁,通过16个线程操作ConcurrentHashMap来提高程序的效率。但是拆分出来的16个HashTable,每个HashTable中还有16条数据,这16条数据是同一把锁。所以如果是查询整个集合或者查询数据较多的情况下,推荐使用ConcurrentHashMap。如果查询某1个2个的话,还是可以直接使用HashTable。还有上面我故意说成16个HashTable,是因为ConcurrentHashMap最高支持分成16段。
在jdk1.8以前,这个ConcurrentHashMap底层原理是 :数组 + 分段锁。
但是在jdk1.8之后,java重写了HashMap 和 ConcurrentHashMap。所以他们的底层原理又有所改变。
1.8以后的ConcurrentHashMap 和 HashMap 底层原理是:数组+链表+红黑树, 只不过一个线程安全,一个不安全。
有兴趣的可以看看这篇文章:彻底搞清楚ConcurrentHashMap的实现原理(含JDK1.7和JDK1.8的区别)
我觉得大概原理也都差不多,只不过性能更优化了,比如说1.8以前的ConcurrentHashMap是直接分段,这样如果数据量较大的情况下,其实效率也还是比较低。1.8之后,加入了红黑树的数据结构,当数据量达到一定程度之后,就使用红黑树存储数据,为后面查询,提高效率。
这是ConcurrentHashMap的put方法。
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
/底层是用的HashTable,这里也说明了,不能有null健 和 null值。
if (key == null || value == null) throw new NullPointerException();
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();
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
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
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;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}