一、解释
1.1 早期
Vector 和 Hashtable
1.1.1 Vector
Vector synchronized修饰方法:
1.1.2 Hashtable
Hashtable synchronized修饰方法:
1.1.3 Collections.synchronizedList()方法
Collections.synchronizedList() 修饰对应的ArrayList、HashMap。
查看源码:
总之,是比历史的容器类优良点,使用的同步代码块。
1.2 如今
1.2.1 ConcurrentHashMap:线程安全的HashMap
1.2.1.1解释
这个是Map的结构图
下面是HashMap的put源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
可以从上面看出,使用的是Node类进行数据的保存。
查看ConCurrentHashMap 的put源码:
final V putVal(K key, V value, boolean onlyIfAbsent) {
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; K fk; V fv;
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)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else if (onlyIfAbsent // check first node without acquiring lock
&& fh == hash
&& ((fk = f.key) == key || (fk != null && key.equals(fk)))
&& (fv = f.val) != null)
return fv;
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);
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;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
从上面可以看出,使用了CAS 和 synchronized 实现了该put方法。 HashMap结构缺少这2个实现机制。
而查看JDK1.7版本源码:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
可以看出使用的是Segment,而Segment继承至 ReentrantLock。
1.2.2 对比
转红黑树需要预值为8,且容量大于64。
1.2. 3 注意点
组合操作有可能造成线程不安全,并不是ConCurrentHashMap不安全,而是过程,贴个代码:
public class OptionsNotSafe implements Runnable {
private static ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
public static void main(String[] args) throws InterruptedException {
scores.put("key", 0);
Thread thread = new Thread(new OptionsNotSafe());
Thread thread1 = new Thread(new OptionsNotSafe());
Thread thread2 = new Thread(new OptionsNotSafe());
thread.start();
thread1.start();
thread2.start();
thread.join();
thread1.join();
thread2.join();
System.out.println(scores);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Integer score = scores.get("key");
int newScores = score + 1;
scores.put("key", newScores);
}
}
}
运行结果:
解析:
替换方法 replace
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
while (true) {
Integer score = scores.get("key");
int newScores = score + 1;
boolean b = scores.replace("key", score, newScores);
if (b) {
break;
}
}
}
}
1.3 CopyOnWriteArrayList线程安全的ArrayList
1.3.1 适用场景
。读操作可以尽可能地块,而写即使慢一线也没有太大关系。
。读多写少:黑名单,每日更新,监听器:迭代操作远多余修改操作。
1.3.2 读写规则
读写锁规则的升级:读取是完全不用加锁的,并且更厉害的是,写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。
1.3.3 缺点
。数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。希望写入数据,马上读到,CopyOnWrite不支持。
。内存占用问题:CopyOnWrite的写是复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存。
下面是源码演示:
功能演示:
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
// ArrayList<Object> objects = new ArrayList<>();
CopyOnWriteArrayList<Object> objects = new CopyOnWriteArrayList<>();
objects.add("1");
objects.add("2");
objects.add("3");
objects.add("4");
objects.add("5");
Iterator<Object> iterator = objects.iterator();
while (iterator.hasNext()) {
System.out.println("list is " + objects);
String next = (String) iterator.next();
if (next.equals("2")) {
objects.remove("5");
}
if (next.equals("3")) {
objects.add("3 found");
}
System.out.println(next);
}
}
}
运行结果:
1.4 并发队列
队列应用场景
。用队列可以在线程间传递数据:生产者消费者模式、银行转账。
1.4.1 BlockingQueue 阻塞队列
。阻塞队列是具有阻塞功能的队列,所以它首先是一个队列,其次是具有阻塞功能。
。通常,阻塞队列的一端是给生产者放数据用,另一端给消费者拿数据用。阻塞队列是线程安全的,所以生产者和消费者都可以是多线程。
。是否有界(容量有多大) : 这是一个非常重要的属性,无界队列意味着里面可以容纳非常多.
。阻塞队列和线程池的关系:阻塞队列是线程池的重要组成部分。
1.4.2 阻塞队列重要的方法
。take() 方法:获取并移除队列的头结点,一旦如果执行take的时候,队列里无数据,则阻塞,直到队列里有数据。
。put() 方法:插入元素。但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间。
代码演示:
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
Interviewer r1 = new Interviewer(queue);
Consumer r2 = new Consumer(queue);
new Thread(r1).start();
new Thread(r2).start();
}
}
class Interviewer implements Runnable {
BlockingQueue<String> queue;
public Interviewer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("10个候选人都来了");
for (int i = 0; i < 10; i++) {
String candidate = "Candidate" + i;
try {
queue.put(candidate);
System.out.println("安排好了" + candidate);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
queue.put("stop");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable{
BlockingQueue<String> queue;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String msg;
try {
while (!(msg = queue.take()).equals("stop")) {
System.out.println(msg + "到了");
}
System.out.println("所有候选人都结束了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
源码分析:
1.4.3 注意点
ArrayBlockingQueue 使用的是同一把锁。
LinkedBlockingQueue使用的是两把锁.
总结: