java 多线程之并发包学习
java 并发包学习
1、简介
在java1.5被引入的 countDownLatch,CyclicBarrier、Semaphore、concurrentHashMap 和 BlockingQueue 工具类。存在于 java.util.cucurrent 包下。
2、 ConcurrentHashMap:
ConcurrentHashMap : 线程安全效率高于hashTable, jdk1.7中使用的是分段锁, 将数据分成多个hashTable,避免了多个线程竞争同一把锁,导致效率低下
在JDK1.7和JDK1.8中的区别:
在JDK1.8主要设计上的改进有以下几点:
- 1、不采用segment分段锁而采用node,锁住node来实现减小锁粒度。
- 2、设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
- 3、使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
- 4、sizeCtl的不同值来代表不同含义,起到了控制的作用。
- 5、采用synchronized而不是ReentrantLock
HashMap、Hashtable、ConccurentHashMap三者的区别
- HashMap线程不安全,数组+链表+红黑树
- Hashtable线程安全,锁住整个对象,数组+链表
- ConccurentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
- HashMap的key,value均可为null,其他两个不行。
3、countDownLatch
countDownLatch 这个类使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了
countDownLatch类中只提供了一个构造器:
//参数count为计数值
public CountDownLatch(int count) { };
类中有三个方法是最重要的:
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
示例:
public class CountDownLatchTest {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
System.out.println("主线程开始执行…… ……");
//第一个子线程执行
ExecutorService es1 = Executors.newSingleThreadExecutor();
es1.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数--
latch.countDown();
}
});
es1.shutdown();
//第二个子线程执行
ExecutorService es2 = Executors.newSingleThreadExecutor();
es2.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
latch.countDown();
}
});
es2.shutdown();
System.out.println("等待两个线程执行完毕…… ……");
try {
// 等待两个线程都执行完毕
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("两个子线程都执行完毕,继续执行主线程");
}
}
4、CyclicBarrier
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
怎么使用 CyclicBarrier?
构造方法
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
- parties 是参与线程的个数
- 第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
重要方法
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
- 线程调用 await() 表示自己已经到达栅栏
- BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
基本使用:
需求: 一个线程组的线程需要等待所有线程完成任务后再继续执行下一次任务
代码实现:
public class CyclicBarrierDemo {
static class TaskThread extends Thread {
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int threadNum = 5;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 完成最后任务");
}
});
for(int i = 0; i < threadNum; i++) {
new TaskThread(barrier).start();
}
}
}
打印结果:
Thread-1 到达栅栏 A
Thread-3 到达栅栏 A
Thread-0 到达栅栏 A
Thread-4 到达栅栏 A
Thread-2 到达栅栏 A
Thread-2 完成最后任务
Thread-2 冲破栅栏 A
Thread-1 冲破栅栏 A
Thread-3 冲破栅栏 A
Thread-4 冲破栅栏 A
Thread-0 冲破栅栏 A
Thread-4 到达栅栏 B
Thread-0 到达栅栏 B
Thread-3 到达栅栏 B
Thread-2 到达栅栏 B
Thread-1 到达栅栏 B
Thread-1 完成最后任务
Thread-1 冲破栅栏 B
Thread-0 冲破栅栏 B
Thread-4 冲破栅栏 B
Thread-2 冲破栅栏 B
Thread-3 冲破栅栏 B
从打印结果可以看出,所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。
CyclicBarrier 使用场景:
可以用于多线程计算数据,最后合并计算结果的场景。
CyclicBarrier 与 CountDownLatch 区别:
- CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
- CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。
5、 Semaphore
Semaphore 是一个计数信号量,必须由获取它的线程释放。常用于限制可以访问某些资源的线程数量,例如通过 Semaphore 限流。
在 semaphore.acquire() 和 semaphore.release()之间的代码,同一时刻只允许制定个数的线程进入。
因为semaphore的构造方法的参数是3,则同一时刻只允许3个线程进入,其他线程等待。
示例:
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class StudySemaphore {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量,只允许 3个线程同时访问
Semaphore semaphore = new Semaphore(3);
for (int i=0;i<10;i++){
final long num = i;
executorService.submit(new Runnable() {
@Override
public void run() {
try {
//获取许可
semaphore.acquire();
//执行
System.out.println("Accessing: " + num);
Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
//释放
semaphore.release();
System.out.println("Release..." + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}
基于链表的无界的同时支持FIFO、LIFO的非阻塞并发双端队列,当许多线程共享对公共集合的访问时,ConcurrentLinkedDeque是一个合适的选择,类比ConcurrentLinkedQueue是针对LinkedBlockingQueue对高并发情况的一种解决方案,ConcurrentLinkedDeque也是同样的地位,都是采用 CAS来替代加锁,甚至ConcurrentLinkedDeque再实现上也与ConcurrentLinkedQueue有很多相似的地方,其中最值得提及的就是,它采用了与ConcurrentLinkedQueue一样的松弛阀值设计(松弛阀值都是1),即head、tail并不总是指向队列的第一个、最后一个节点,而是保持head/tail距离第一个/最后一个节点的距离不超过1个节点的距离,从而减少了更新head/tail指针的CAS次数。Java Doc指出理解ConcurrentLinkedQueue的实现是理解该类实现的先决条件,所以最好先理解了ConcurrentLinkedQueue再来理解该类。
ConcurrentLinkedQueue 与 ConcurrentLinkedDeque 的区别:
- ConcurrentLinkedQueue: 是单向链表结构的无界并发队列。元素操作按照 FIFO (first-in-first-out 先入先出) 的顺序。适合“单生产,多消费”的场景。内存一致性遵循对ConcurrentLinkedQueue的插入操作先行发生于(happen-before)访问或移除操作。
- ConcurrentLinkedDeque: 是双向链表结构的无界并发队列。与 ConcurrentLinkedQueue 的区别是该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除)。适合“多生产,多消费”的场景。内存一致性遵循对 ConcurrentLinkedDeque 的插入操作先行发生于(happen-before)访问或移除操作
ConcurrentLinkedDeque 的一些方法介绍
1、add(E e):在此deque的尾部插入指定的元素,返回值为Boolean。
2、addFirst(E e):在此deque前面插入指定的元素。
3、addLast(E e):在此deque的末尾插入指定的元素。
4、clear():从这个deque中删除所有的元素。
5、contains(Object o):返回 true如果这个deque包含至少一个元素 e ,返回值为Boolean。
6、descendingIterator():以相反的顺序返回此deque中的元素的迭代器,返回值为Iterator。
7、element():检索但不删除由此deque表示的队列的头部(换句话说,该deque的第一个元素)。
8、getFirst():检索,但不删除,这个deque的第一个元素。
9、getLast():检索,但不删除,这个deque的最后一个元素。
10、isEmpty():如果此集合不包含元素,则返回 true 。
11、iterator():以正确的顺序返回此deque中的元素的迭代器,返回值为Iterator 。
12、offer(E e):在此deque的尾部插入指定的元素,返回值为boolean。
13、offerFirst(E e):在此deque前面插入指定的元素,返回值为boolean。
14、offerLast(E e):在此deque的末尾插入指定的元素,返回值为boolean。
15、peek():检索但不删除由此deque表示的队列的头(换句话说,该deque的第一个元素),如果此deque为空,则返回 null 。
16、peekFirst():检索但不删除此deque的第一个元素,如果此deque为空,则返回 null 。
17、peekLast():检索但不删除此deque的最后一个元素,如果此deque为空,则返回 null 。
18、poll():检索并删除由此deque表示的队列的头部(换句话说,该deque的第一个元素),如果此deque为空,则返回 null 。
19、pollFirst():检索并删除此deque的第一个元素,如果此deque为空,则返回 null 。
20、pollLast():检索并删除此deque的最后一个元素,如果此deque为空,则返回 null 。
21、pop():从这个deque表示的堆栈中弹出一个元素。
22、push(E e):将元素推送到由此deque代表的堆栈(换句话说,在该deque的头部),如果可以立即执行,而不违反容量限制,
则抛出 IllegalStateException如果当前没有可用空间)。
23、remove():检索并删除由此deque表示的队列的头(换句话说,该deque的第一个元素)。
24、remove(Object o):删除第一个元素 e ,使 o.equals(e) ,如果这样一个元素存在于这个deque,返回值为boolean。
25、removeFirst():检索并删除此deque的第一个元素。
26、removeFirstOccurrence(Object o):删除第一个元素 e ,使 o.equals(e) ,如果这样一个元素存在于这个deque,返回值为boolean。
27、removeLast():检索并删除此deque的最后一个元素。
28、size():返回此deque中的元素数。
7、 LinkedBlockingDeque
一、LinkedBlockingDeque介绍
LinkedBlockingDeque是双向链表实现的双向并发阻塞队列。该阻塞队列同时支持FIFO(先进先出)和FILO(先进后出)两种操作方式,即可以从队列的头和尾同时操作(插入/删除);并且,该阻塞队列是支持线程安全。还有,LinkedBlockingDeque还是可选容量的(防止过度膨胀),即可以指定队列的容量。如果不指定,默认容量大小等于Integer.MAX_VALUE(@Native public static final int MAX_VALUE = 0x7fffffff)。
二、LinkedBlockingDeque方法介绍(超详细)
1、add(E e) :在不违反容量限制的情况下,将指定的元素插入此双端队列的末尾,返回值为Boolean。
2、addFirst(E e) :如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的开头;如果当前没有空间可用,则抛出 IllegalStateException。
3、addLast(E e) :如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的末尾;如果当前没有空间可用,则抛出 IllegalStateException。
4、clear() :以原子方式 (atomically) 从此双端队列移除所有元素。
5、contains(Object o) :如果此双端队列包含指定的元素,则返回 true。
6、descendingIterator() :返回在此双端队列的元素上以逆向连续顺序进行迭代的迭代器,返回值为 Iterator。
7、element() :获取但不移除此双端队列表示的队列的头部。
8、getFirst() :获取,但不移除此双端队列的第一个元素。
9、getLast() :获取,但不移除此双端队列的最后一个元素。
10、iterator():返回在此双端队列元素上以恰当顺序进行迭代的迭代器,返回值为 Iterator。
11、offer(E e) :如果立即可行且不违反容量限制,则将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),并在成功时返回 true;如果当前没有空间可用,则返回 false。
12、offer(E e, long timeout, TimeUnit unit) :将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),必要时将在指定的等待时间内一直等待可用空间,返回值为Boolean。
13、offerFirst(E e) :如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的开头,并在成功时返回 true;如果当前没有空间可用,则返回 false。
14、offerFirst(E e, long timeout, TimeUnit unit) :将指定的元素插入此双端队列的开头,必要时将在指定的等待时间内等待可用空间,返回值为Boolean。
15、offerLast(E e) : 如果立即可行且不违反容量限制,则将指定的元素插入此双端队列的末尾,并在成功时返回 true;如果当前没有空间可用,则返回 false。
16、offerLast(E e, long timeout, TimeUnit unit) : 将指定的元素插入此双端队列的末尾,必要时将在指定的等待时间内等待可用空间,返回值为Boolean。
17、peek() :获取但不移除此双端队列表示的队列的头部(即此双端队列的第一个元素);如果此双端队列为空,则返回 null。
18、peekFirst() :获取,但不移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。
19、peekLast() :获取,但不移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。
20、poll() :获取并移除此双端队列表示的队列的头部(即此双端队列的第一个元素);如果此双端队列为空,则返回 null。
21、poll(long timeout, TimeUnit unit) :获取并移除此双端队列表示的队列的头部(即此双端队列的第一个元素),如有必要将在指定的等待时间内等待可用元素。
22、pollFirst() : 获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。
23、pollFirst(long timeout, TimeUnit unit) :获取并移除此双端队列的第一个元素,必要时将在指定的等待时间等待可用元素。
24、pollLast() :获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。
25、pollLast(long timeout, TimeUnit unit) :获取并移除此双端队列的最后一个元素,必要时将在指定的等待时间内等待可用元素。
26:pop() :从此双端队列所表示的堆栈中弹出一个元素。
27、push(E e) :将元素推入此双端队列表示的栈。
28、put(E e) :将指定的元素插入此双端队列表示的队列中(即此双端队列的尾部),必要时将一直等待可用空间。
29:putFirst(E e) : 将指定的元素插入此双端队列的开头,必要时将一直等待可用空间。
30、putLast(E e) :将指定的元素插入此双端队列的末尾,必要时将一直等待可用空间。
31、remove() :获取并移除此双端队列表示的队列的头部。
32、remove(Object o) :从此双端队列移除第一次出现的指定元素,返回值为Boolean。
33、removeFirst() :获取并移除此双端队列第一个元素。
34、removeLast() :获取并移除此双端队列的最后一个元素。
35、size() :返回此双端队列中的元素数。
36、take() :获取并移除此双端队列表示的队列的头部(即此双端队列的第一个元素),必要时将一直等待可用元素。
37、takeFirst() :获取并移除此双端队列的第一个元素,必要时将一直等待可用元素。
38、takeLast() :获取并移除此双端队列的最后一个元素,必要时将一直等待可用元素。
39、Object[] toArray() :返回以恰当顺序(从第一个元素到最后一个元素)包含此双端队列所有元素的数组。
40、toString() :返回此 collection 的字符串表示形式,返回值为String。
8、LinkedBlockingQueue和LinkedBlockingDeque区别
两个都是队列,只不过前者(LinkedBlockingQueue)只能一端出一端入,后者则可以两端同时出入,并且都是结构改变线程安全的队列。
其实两个队列从实现思想上比较容易理解,有以下特点:
- ①、链表结构(动态数组)
- ②、通过ReentrantLock实现锁
- ③、利用Condition实现队列的阻塞等待,唤醒
阻塞式队列与非阻塞队列的区别:
阻塞式队列:
入列(存):阻塞式队列,如果存放的队列超出队列的总数,是时候会进行等待(阻塞)。当队列达到总数的时候,入列(生产者)会进行阻塞。这时候只有当消费者消费了队列中的队列之后,生产者才可以继续往队列中存放队列。
出列(取):如果获取队列为空的情况下,这时候也会进行等待(阻塞)。这时候队列中没有队列,消费者无法消费队列,只有生产者往对队列中存放队列之后,消费者才可以进行消费。
队列中的队列如果被消费了就会从队列中删除掉。
ArrayBlockingQueue
ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。
ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。
LinkedBlockingQueue
LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。和ArrayBlockingQueue一样,LinkedBlockingQueue 也是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。
PriorityBlockingQueue
PriorityBlockingQueue是一个没有边界的队列,它的排序规则和 java.util.PriorityQueue一样。需要注 意,PriorityBlockingQueue中允许插入null对象。
所有插入PriorityBlockingQueue的对象必须实现 java.lang.Comparable接口,队列优先级的排序规则就 是按照我们对这个接口的实现来定义的。另外,我们可以从PriorityBlockingQueue获得一个迭代器Iterator,但这个迭代器并不保证按照优先级顺 序进行迭代。
SynchronousQueue
SynchronousQueue队列内部仅允许容纳一个元素。当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费。
9、AtomicBoolean, AtomicInteger,AtomicLong,AtomicLongArray, AtomicReference
java.util.concurrent.atomic 的包里有AtomicBoolean, AtomicInteger,AtomicLong,AtomicLongArray, AtomicReference等原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理.
在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
import java.util.concurrent.atomic.AtomicInteger;
/**
* 来看AtomicInteger提供的接口。
//获取当前的值
public final int get()
//取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement()
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta)
getAndAdd()方法与AddAndGet方法
AtomicInteger atomicInteger = new AtomicInteger(123);
System.out.println(atomicInteger.get()); --123
System.out.println(atomicInteger.getAndAdd(10)); --123 获取当前值,并加10
System.out.println(atomicInteger.get()); --133
System.out.println(atomicInteger.addAndGet(10)); --143 获取加10后的值,先加10
System.out.println(atomicInteger.get()); --143
getAndDecrement()和DecrementAndGet()方法
AtomicInteger atomicInteger = new AtomicInteger(123);
System.out.println(atomicInteger.get()); --123
System.out.println(atomicInteger.getAndDecrement()); --123 获取当前值并自减
System.out.println(atomicInteger.get()); --122
System.out.println(atomicInteger.decrementAndGet()); --121 先自减再获取减1后的值
System.out.println(atomicInteger.get()); --121