传统艺能,点到为止
Vector和ArrayList原理都是由数组实现的,查询速度块,增加、修改和删除速度慢。
最大的区别在于线程安全问题,Vector是线程安全的,ArrayList是线程不安全的,但ArrayList效率更高。
Vector是线程安全的那么必然是上了锁的类集合。
点进Vector类,看get set add方法的实现,我们发现他们的方法都被synchronized所修饰。多个线程使用Vector类,只要有一个线程在操作,其他线程都读不了数据,还引发锁资源竞争,因此效率很低。
/**
* Returns the element at the specified position in this Vector.
*
* @param index index of the element to return
* @return object at the specified index
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
* @since 1.2
*/
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
/**
* Replaces the element at the specified position in this Vector with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
* @since 1.2
*/
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
/**
* 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一看就知道,是没有做任何的同步
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
HashTable线程安全,HashMap线程不安全
//todo 以后在细写吧。
链表+数组,链表做增加删除, HashCode取模得到下标位置,一致性取模算法。
很明显可以看到put方法加了synchronized关键字。
public synchronized V put(K key, V value) {
// Make sure the value is not 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;
}
没有synchronized关键字
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with key, or
* null if there was no mapping for key.
* (A null return can also indicate that the map
* previously associated null with key.)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
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;
}
是Collections中的静态类,可以将不安全Map集合转变为安全的集合
//todo Collections再单独拿出来写吧…
原理无非就是用了Object锁的synchronized代码块
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {
return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {
return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {
return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {
return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {
return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {
return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {
return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {
m.putAll(map);}
}
public void clear() {
synchronized (mutex) {
m.clear();}
}
//...
}
jdk1.5之后产生了许多的java并发包,ConcurrentHashMap就是其中之一,为解决1.2的Hashtable虽然是线程安全的但是效率非常低,造成锁的资源竞争问题。
分段锁,将一个整体Map拆分成多个小的Hashtable,默认分成16段(上限为16),我们具体的假设,如Map的下标0到4分为一个Hashtable,5-9分为一个Hashtable,10-14分为一个Hashtable…多线程的情况下,线程①查询下标2,线程②查询下标5,线程③查询下标10,那么这三个线程并不使用同一把锁,相比之前的Hashtable,Hashtable三个线程共用同一把锁,ConcurrentHashMap减少了锁的资源竞争,因此效率得到了提高。并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容。volatile不只是起到可见性的作用,还起禁止重排序功能。
CountDownLatch类位于concurrent包下,利用它可以实现类似计数器的功能,比如有一个任务A,它要等其他4个任务执行完毕后才能执行,此时就可以使用CountDownLatch来实现这种功能了。相比join可能会更方便一些。
使用起来很简单,实例化CountDownLatch,给初始值,使用countDown函数计数减一,使用countDownLatch.await()进行阻塞判断,大于0一直阻塞,小于等于0停止阻塞。
代码见下:
import lombok.SneakyThrows;
import java.util.concurrent.CountDownLatch;
public class CountDown {
@SneakyThrows
public static void main(String[] args) {
// 定义计数器
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread thread1 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println("我是子线程1执行任务");
Thread.sleep(10);
System.out.println("我是子线程1执行任务");
// 计数器减一
countDownLatch.countDown();
}
});
Thread thread2 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println("我是子线程2执行任务");
Thread.sleep(10);
System.out.println("我是子线程2执行任务");
countDownLatch.countDown();
}
});
thread1.start();
thread2.start();
// 如果不为0,阻塞
countDownLatch.await();
System.out.println("主线程开始执行任务");
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
System.out.println(i);
}
System.out.println("主线程执行任务结束");
}
}
jdk1.5并发包中的类,用的不多,了解即可,也是做计数用的,当我们线程到达一定次数,开始并行执行。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import java.util.concurrent.CyclicBarrier;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
class MyThread extends Thread {
private CyclicBarrier cyclicBarrier;
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",开始写入任务");
// 模拟任务执行时间
Thread.sleep(1);
// await大于0时线程阻塞和计数减一,当为0的时候,所有线程共同并行
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + ",写入任务结束...");
}
}
public class CyclicTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(50);
for (int i = 0; i < 50; i++) {
new MyThread(cyclicBarrier).start();
}
}
}
Semaphore属于并发包中的一类,Semaphore可以看作是一种基于计数的信号量,可以设定一个阈值,这个阈值表示最多支持几个线程访问,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。
Semaphore可以用来构建一些对象池,资源池,比如数据库连接池,Semaphore计数为1,将变成类似互斥锁的机制。
Semaphore这个概念应该并不陌生。
掌握主要方法acquire、availablePermits、release,获取和释放资源
acquire 获取资源,计数-1,阻塞等待
release释放资源计数+1
availablePermits返回此Semaphore对象中当前可用的许可数,许可的数量有可能实时在改变,并不是固定的数量。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import java.util.Random;
import java.util.concurrent.Semaphore;
@Data
@AllArgsConstructor
class Parent implements Runnable {
private Semaphore semaphore;
private String name;
@SneakyThrows
@Override
public void run() {
int i = semaphore.availablePermits();
if (i > 0) {
System.out.println(getName() + " ,i > 0");
} else {
System.out.println(getName() + " ,i < 0,wait..");
}
semaphore.acquire();
System.out.println(getName() + " ,entering...");
Thread.sleep(new Random().nextInt(10000));
System.out.println(getName() + " ,finish.");
semaphore.release();
}
}
public class SemaTest {
public static void main(String[] args) {
Semaphore s = new Semaphore(3);
for (int i = 1; i <= 10; i++) {
new Thread(new Parent(s, i+"")).start();
}
}
}
并发队列也是并发包中的,他们都是线程安全的。
生产消费者模型中的缓冲buff就可以看作是一个并发队列
队列遵循规则:先进先出
Array数组规定长度,不能超过长度,就是有界的
无界支持无限制存放。
生产者写入满的时候,即队列满了,线程进行等待;消费者当队列为空的时候,也进行等待,就是阻塞队列。
非阻塞的,满了或者空了线程不等待直接挂掉。
非阻塞式无界限安全队列 ConcurrentLinkedDeque和ConcurrentLinkedQueue,不同的是ConcurrentLinkedDueue是双向链表,因此ConcurrentLinkedDueue既可以当做队列也可当做栈来使用。
public class Qu {
public static void main(String[] args) {
ConcurrentLinkedDeque<String> concurrentLinkedDeque = new ConcurrentLinkedDeque<>();
concurrentLinkedDeque.offer("张三");
concurrentLinkedDeque.offer("李四");
System.out.println(concurrentLinkedDeque.size());
System.out.println(concurrentLinkedDeque.poll());
System.out.println(concurrentLinkedDeque.size());
System.out.println(concurrentLinkedDeque.poll());
System.out.println(concurrentLinkedDeque.size());
}
}
BlockingQueue
常用的四个类是ArrayBlockingQueue、LinkedBlockingQueue,PriorityBlockingQueue和SynchronizedQueue
public class Qu {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);
queue.add("张三");
queue.add("李四");
queue.add("王五");
// 可阻塞的队列,超过界限超时2秒,挂掉
queue.offer("老六",2,TimeUnit.SECONDS);
System.out.println("阻塞2秒后结束");
System.out.println(queue.size());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
}
}
BlockingQueue可阻塞,并且时间有界限,ConcurrentLinkedDeque不阻塞
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Thread.sleep;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
class ProducerThread extends Thread {
private BlockingQueue<String> blockingQueue;
private static AtomicInteger count = new AtomicInteger();
private volatile Boolean allowProducing = true;
@SneakyThrows
@Override
public void run() {
System.out.println("生产者线程启动");
while (allowProducing) {
System.out.println("正在生产队列");
String data = count.incrementAndGet() + "";
boolean offer = blockingQueue.offer(data);
if (offer) {
System.out.println("生产者添加队列成功");
} else {
System.out.println("生产者添加队列失败");
}
sleep(1000);
}
System.out.println("生产者线程停止");
}
public void stopThread() {
this.allowProducing = false;
}
}
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
class Consumer extends Thread {
private BlockingQueue<String> blockingQueue;
private volatile Boolean allowConsume = true;
@SneakyThrows
@Override
public void run() {
System.out.println("消费者线程启动");
while (allowConsume) {
String data = blockingQueue.poll(2, TimeUnit.SECONDS);
if (data != null) {
System.out.println("消费者获取数据: " + data);
} else {
System.out.println("消费者获取数据失败");
this.allowConsume = false;
}
sleep(1000);
}
}
public void stopThread() {
this.allowConsume = false;
}
}
public class TestScz {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(10);
ProducerThread producerThread1 = new ProducerThread(blockingDeque, true);
ProducerThread producerThread2 = new ProducerThread(blockingDeque, true);
Consumer consumer = new Consumer(blockingDeque, true);
producerThread1.start();
producerThread2.start();
consumer.start();
Thread.sleep(10 * 1000);
producerThread1.stopThread();
producerThread2.stopThread();
}
}