该包让java并发编程更轻松。
一、Blocking Queue
1、BlockingQueue介绍
阻塞式队列,通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。
操作BlockingQueue时,当操作不能立即执行,各个方法表现也不同,如下:
抛异常 | 返回特定值 | 阻塞 | 超时 | |
插入 | add(o) | offer(o) | put(o) | offer(o,timeout,timeunit) |
移除 | remove(o) | poll(o) | task(o) | poll(timeout,timeunit) |
检查 | element(o) | peek(o) |
例如,当BlockingQueue中没有数据时,调用task()拿数据则会产生阻塞,直到有数据可以拿。
2、实现BlockingQueue接口的具体类
类名 | 类中文名 | 特点 |
ArrayBlockingQueue | 数组阻塞队列 | 基于数组实现,有大小限制,且定义好后无法修改大小。 |
DelayQueue | 延迟队列 | 无大小限制 |
LinkedBlockingDeque | 链阻塞队列 | 基于链式结构实现,无大小限制 |
PriorityBlockingQueue | 有优先级的阻塞队列 | |
SynchronousQueue | 同步队列 |
3、双端队列
二、并发Map,ConcurrentMap
ConcurrentHashMap
具备 HashTable的线程安全,也具备 HashMap的并发性
在对ConcurrentHashMap 进行读和写时,并不会锁住整个map,而是把真个ConcurrentHashMap分成多块,只对正在操作的那一块进行锁定。
三、闭锁 (递减锁) CountDownLatch
初始化时给定一个数量,每调用一次。countDown(),数量就减一。配合 await()方法使用
await()方法的作用:阻塞,直到CountDownLatch的数量减为零,才会继续往下执行。
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(3);
latch.countDown();
latch.countDown();
latch.countDown();
//阻塞,直到3次机会用完
latch.await();
System.out.println("三次减完了!");
}
四、栅栏 CyclicBarrier
初始化时给定一个数量。
当调用 await() 时产生阻塞,直到调用 await() 方法的次数达到这个数量时,所有调用await()的方法处,都会继续往下执行。
下面的例子:多个线程全部到达栅栏后,各个线程才会继续执行。
package day01Current;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) throws Exception {
CyclicBarrier cb = new CyclicBarrier(2);
new Thread(new T1(cb)).start();
new Thread(new T2(cb)).start();
}
}
class T1 implements Runnable {
private CyclicBarrier cb = null;
public T1(CyclicBarrier cb) {
this.cb = cb;
}
public void run() {
try {
Thread.sleep(2000);
System.out.println("T1到达栅栏!");
cb.await();
System.out.println("T1出了栅栏!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class T2 implements Runnable {
private CyclicBarrier cb = null;
public T2(CyclicBarrier cb) {
this.cb = cb;
}
public void run() {
try {
Thread.sleep(5000);
System.out.println("T2到达栅栏!");
cb.await();
System.out.println("T2出了栅栏!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
五、交换机 Exchanger
创建一个交换机实例,两个线程可以通过这个交换机实例互相交换对象。
交换对象是通过 Exchanger实例的 exchange 方法,该方法会把对象交给另一个线程的同时,返回从另一个线程获取的对象。
代码示例如下:
package day01Current;
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) throws Exception {
Exchanger<String> exc = new Exchanger<String>();
new Thread(new T1(exc)).start();
new Thread(new T2(exc)).start();
}
}
class T1 implements Runnable {
private Exchanger<String> exc = null;
public T1(Exchanger<String> exc) {
this.exc = exc;
}
public void run() {
try {
String obj = exc.exchange("线程T1生成的对象");
System.out.println("T1交换得到的对象为: " + obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class T2 implements Runnable {
private Exchanger<String> exc = null;
public T2(Exchanger<String> exc) {
this.exc = exc;
}
public void run() {
try {
String obj = exc.exchange("线程T2生成的对象");
System.out.println("T2交换得到的对象为: " + obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、 信号量 Semaphore
该类是一个 计数信号量。
初始化时指定一个数量的 "许可" ,每调用一次 acquire() 方法,一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。如果没有 "许可" 可以取走,则 acquire() 会阻塞。release()方法不会阻塞。
release()方法类似于生产者,每调用一次release()方法,就会增加一个许可,且可以超出定义时的个数限制。所以acquire()和release()两个方法一定要配对使用。
1. 保护一个重要(代码)部分,防止一次超过 N 个线程进入。
Semaphore semaphore = new Semaphore(1);
//1、获取执行许可
semaphore.acquire();
//2、执行被保护的代码块
//3、执行完后返还许可
semaphore.release();
2. 在两个线程之间发送信号
想将一个信号量用于在两个线程之间传送信号,通常应该用一个线程调用 acquire() 方法,而另一个线程调用 release() 方法。
这样可以对多个线程进行协调。