并发包
- 在 JDK 的并发包里提供了几个非常有用的并发容器和并发工具类,供我们在多线程开发中进行使用;
1. CopyOnWriteArrayList
a. ArrayList
- ArrayList 的线程不安全,最终结果可能会抛异常,或者最终集合大小是不正确的:
import java.util.ArrayList;
import java.util.List;
class MyThread extends Thread {
public static List<Integer> list = new ArrayList<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("添加完毕!");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("最终集合的长度:" + MyThread.list.size());
}
}
b. CopyOnWriteArrayList
- CopyOnWriteArrayList 是线程安全的,结果始终是正确的;
import java.util.concurrent.CopyOnWriteArrayList;
class MyThread extends Thread {
public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
System.out.println("添加完毕!");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("最终集合的长度:" + MyThread.list.size());
}
}
2. CopyOnWriteArraySet
a. HashSet
- HashSet 仍然是线程不安全的, 最终结果可能会抛异常,也可能最终的长度是错误的:
import java.util.HashSet;
import java.util.Set;
class MyThread extends Thread {
public static Set<Integer> set = new HashSet<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
set.add(i);
}
System.out.println("添加完毕!");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
for (int i = 10000; i < 20000; i++) {
MyThread.set.add(i);
}
Thread.sleep(1000 * 3);
System.out.println("最终集合的长度:" + MyThread.set.size());
}
}
b. CopyOnWriteArraySet
- CopyOnWriteArraySet是线程安全的,可以看到结果总是正确的:
import java.util.concurrent.CopyOnWriteArraySet;
class MyThread extends Thread {
public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
set.add(i);
}
System.out.println("添加完毕!");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
for (int i = 10000; i < 20000; i++) {
MyThread.set.add(i);
}
Thread.sleep(1000 * 3);
System.out.println("最终集合的长度:" + MyThread.set.size());
}
}
3. ConcurrentHashMap
a. HashMap
- HashMap 是线程不安全的,运行结果可能会出现异常、或者结果不准确:
import java.util.HashMap;
import java.util.Map;
class MyThread extends Thread {
public static Map<Integer, Integer> map = new HashMap<>();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
map.put(i, i);
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
for (int i = 10000; i < 20000; i++) {
MyThread.map.put(i, i);
}
Thread.sleep(1000 * 2);
System.out.println("map最终大小:" + MyThread.map.size());
}
}
b. Hashtable
- Hashtable 是 JDK 1.1 提供的一个早期的线程安全的类,它把 remove() put() get() 做成了同步方法,保证了自身线程安全性,所以虽然是线程安全的,但效率低;
public synchronized V put(K key, V value)
public synchronized V get(Object key)
- 下例中,我们加入了"计时",能看到结果是正确的,但耗时较长:
import java.util.Hashtable;
import java.util.Map;
class MyThread extends Thread {
public static Map<Integer, Integer> map = new Hashtable<>();
@Override
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
map.put(i, i);
}
long end = System.currentTimeMillis();
System.out.println((end - start) + " 毫秒");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000 * 20);
System.out.println("map的最终大小:" + MyThread.map.size());
}
}
c. ConcurrentHashMap
- 改用 ConcurrentHashMap,可以看到效率提高了很多:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
class MyThread extends Thread {
public static Map<Integer, Integer> map = new ConcurrentHashMap<>();
@Override
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
map.put(i, i);
}
long end = System.currentTimeMillis();
System.out.println((end - start) + " 毫秒");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new MyThread().start();
}
Thread.sleep(1000 * 20);
System.out.println("map的最终大小:" + MyThread.map.size());
}
}
i. HashTable 与 ConcurrentHashMap
- Hashtable 容器使用 synchronized 来保证线程安全,但在线程竞争激烈的情况下 Hashtable 的效率非常低下。因为当一个线程访问 Hashtable 的同步方法,其他线程也访问 Hashtable 的同步方法时,会进入阻塞状态。如线程1使用 put 进行元素添加,线程2不但不能使用 put 方法添加元素,也不能使用 get 方法来获取元素,所以竞争越激烈效率越低:
![Hashtable](https://img-blog.csdnimg.cn/20200307214515266.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JlZ2lubw==,size_16,color_FFFFFF,t_70)
- ConcurrentHashMap 高效的原因:CAS + 局部(synchronized)锁定:
![ConcurrentHashMap](https://img-blog.csdnimg.cn/20200307214556605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JlZ2lubw==,size_16,color_FFFFFF,t_70)
4. CountDownLatch
- CountDownLatch 允许一个或多个线程等待其他线程完成操作;
- 例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行;
a. 构造方法
public CountDownLatch(int count)
初始化一个指定计数器的 CountDownLatch 对象;
b. 重要方法
public void await() throws InterruptedException
public void countDown()
- CountDownLatch 中 count down 是倒数的意思,latch 则是门闩的含义,整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉;
- CountDownLatch 是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用 countDown() 方法让计数器-1,当计数器到达0时,调用 CountDownLatch;
- await() 方法的线程阻塞状态解除,继续执行;
c. 应用
i. 应用场景
- CountDownLatch 和 wait() 相比:一般 CountDownLatch 用于一个线程等待多个线程结束时或指定多个线程按顺序进行;
ii. 依次打印案例
import java.util.concurrent.CountDownLatch;
class ThreadA extends Thread {
private CountDownLatch down;
public ThreadA(CountDownLatch down) {
this.down = down;
}
@Override
public void run() {
System.out.println("A");
try {
down.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");
}
}
class ThreadB extends Thread {
private CountDownLatch down;
public ThreadB(CountDownLatch down) {
this.down = down;
}
@Override
public void run() {
System.out.println("B");
down.countDown();
}
}
public class Test {
public static void main(String[] args) {
CountDownLatch down = new CountDownLatch(1);
new ThreadA(down).start();
new ThreadB(down).start();
}
}
5. CyclicBarrier
- CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行;
- 例如:公司召集5名员工开会,等5名员工都到了,会议开始。我们创建5个员工线程,1个开会线程,几乎同时启动,使用 CyclicBarrier 保证5名员工线程全部执行后,再执行开会线程;
a. 构造方法
public CyclicBarrier(int parties, Runnable barrierAction)
用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景;
b. 重要方法
public int await()
c. 应用
i. 应用场景
- CyclicBarrier 和 wait() 相比:一般 CyclicBarrier 用于一个线程等待多个线程结束时;
- CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。例如,使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作;
ii. 员工开会案例
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
class PersonThread extends Thread {
private CyclicBarrier cbRef;
public PersonThread(CyclicBarrier cbRef) {
this.cbRef = cbRef;
}
@Override
public void run() {
try {
Thread.sleep((int) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 到了! ");
cbRef.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
class MeetingThread extends Thread {
@Override
public void run() {
System.out.println("好了,人都到了,开始开会......");
}
}
public class Test {
public static void main(String[] args) {
CyclicBarrier cbRef = new CyclicBarrier(5, new MeetingThread());
PersonThread p1 = new PersonThread(cbRef);
PersonThread p2 = new PersonThread(cbRef);
PersonThread p3 = new PersonThread(cbRef);
PersonThread p4 = new PersonThread(cbRef);
PersonThread p5 = new PersonThread(cbRef);
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
}
}
6. Semaphore
- Semaphore(旗语)的主要作用是控制线程的并发数量;
- Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目;
a. 构造方法
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
b. 重要方法
public void acquire() throws InterruptedException
public void release()
c. 应用
i. 应用场景
- synchronized 可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行,而 Semaphore 可以设置同时允许几个线程执行;
ii. 同时允许1个线程执行
import java.util.concurrent.Semaphore;
class Service {
private Semaphore semaphore = new Semaphore(1);
public void testMethod() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()
+ " 进入 时间=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()
+ " 结束 时间=" + System.currentTimeMillis());
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
for (int i = 1; i <= 5; i++) {
ThreadA a = new ThreadA(service);
a.setName("线程 " + i);
a.start();
}
}
}
iii. 同时允许2个线程同时执行
- 只修改Service类,将 new Semaphore(1) 改为2即可:
import java.util.concurrent.Semaphore;
class Service {
private Semaphore semaphore = new Semaphore(2);
public void testMethod() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()
+ " 进入 时间=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()
+ " 结束 时间=" + System.currentTimeMillis());
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
for (int i = 1; i <= 5; i++) {
ThreadA a = new ThreadA(service);
a.setName("线程 " + i);
a.start();
}
}
}
7. Exchanger
- Exchanger(交换者)是一个用于线程间协作的工具类,用于进行线程间的数据交换;
- 这两个线程通过 exchange() 方法交换数据,如果第一个线程先执行exchange() 方法,它会一直等待第二个线程也执行 exchange() 方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方;
a. 构造方法
public Exchanger()
b. 重要方法
public V exchange(V x)
c. 应用
i. 使用场景
- 可以做数据校对工作;
- 比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,并对两个文件数据进行校对,看看是否录入一致;
ii. exchange() 方法的阻塞特性
import java.util.concurrent.Exchanger;
class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");
System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
a.start();
}
}
iii. exchange() 方法执行交换
import java.util.concurrent.Exchanger;
class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");
System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
private Exchanger<String> exchanger;
public ThreadB(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程B欲传递值'礼物B'给线程A,并等待线程A的值...");
System.out.println("在线程B中得到线程A的值=" + exchanger.exchange("礼物B"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
ThreadB b = new ThreadB(exchanger);
a.start();
b.start();
}
}
iv. exchange() 方法的超时
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值,只等5秒...");
System.out.println("在线程A中得到线程B的值 =" + exchanger.exchange("礼物A",5, TimeUnit.SECONDS));
System.out.println("线程A结束!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("5秒钟没等到线程B的值,线程A结束!");
}
}
}
public class Test {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
a.start();
}
}