参考文章:https://blog.csdn.net/androidsj/article/details/80334497
并发编程juc包学习4-并发集合类
线程安全的集合,为了更好的实现集合的高兵法访问处理,创建了一组心的集合工具类。
➣ List和Set集合:
➣ CopyOnWriteArrayList相当于线程安全的ArrayList,实现了List接口。
CopyOnWriteArrayList是支持高并发的;
➣ CopyOnWriteArraySet相当于线程安全的HashSet,它继承了AbstractSet类,
CopyOnWriteArraySet内部包含一个CopyOnWriteArrayList对象,
它是通过CopyOnWriteArrayList实现的。
➣ Map集合:
➣ ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);
它继承于AbstractMap类,并且实现ConcurrentMap接口。
ConcurrentHashMap是通过“锁分段”来实现的,它支持并发;
➣ ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap);
它继承于AbstactMap类,并且实现ConcurrentNavigableMap接口。
ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发;
➣ ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);
它继承于AbstractSet,并实现了NavigableSet接口。
ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发;
➣ Queue队列:
➣ ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列;
➣ LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按FIFO(先进先出)排序元素;
➣ LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,
该阻塞队列同时支持FIFO和FILO两种操作方式;
➣ ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按FIFO(先进先出)排序元素。
➣ ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
原来collection的线程问题
不可实现多线程的增删,在并发环境下对同一个对象操作容易出问题.
/**
* 会抛出异常java.util.ConcurrentModificationException
* ArrayList不支持多线程并发的操作.该异常是由于数据顺序编号不符合导致的
*/
@Test
public void testArrayList() {
List<Integer> sList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int index = i;
new Thread(() -> {
sList.add(index);
}).start();
}
for (Integer integer : sList) {
System.out.println(integer + "\t");
}
}
CopyOnWriteArrayList
相当于线程安全的List,具有List有序可重复的特点.
/**
* 添加内容可以重复
*/
@Test
public void testCopyOnWriteArrayList() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
List<String> iList = new CopyOnWriteArrayList<>();
for (int i = 0; i < 2; i++) {
int index = i;
new Thread(() -> {
for (int j = 0; j < 10; j++) {
//iList.add("线程:"+Thread.currentThread().getName() + "-" + index + "-添加的内容" + j);
iList.add("线程:"/*+Thread.currentThread().getName() + "-" + index */ + "-添加的内容" + j);
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
for (String s : iList) {
System.out.println(s + "\t");
}
}
CopyOnWriteArraySet
线程安全的Set,具有set的特点,不可重复
/**
* 添加内容不可重复
*/
@Test
public void testCopyOnWriteSet() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Set<String> sSet = new CopyOnWriteArraySet<>();
for (int i = 0; i < 2; i++) {
int index = i;
new Thread(() -> {
for (int j = 0; j < 10; j++) {
//sSet.add("线程:"+Thread.currentThread().getName() + "-" + index + "-添加的内容" + j);
//当添加的内容相同时和CopyOnWriteArrayList不同
sSet.add("线程:"/*+Thread.currentThread().getName() + "-" + index */ + "-添加的内容" + j);
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
for (String s : sSet) {
System.out.println(s + "\t");
}
}
ConcurrentHashMap
相当于线程安全的HashMap
/**
* 整体特征:写的时候同步写入,使用独占锁,读的时候为了保证性能使用了共享锁。
*/
@Test
public void testConcurrentHashMap() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Map<String, Object> map = new ConcurrentHashMap<>();
for (int i = 0; i < 2; i++) {
int x = i;
new Thread(() -> {
for (int j = 0; j < 10; j++) {
//虽然有两个线程,每个put10个.当时当key相同时,value就被替换了.so,只会有10个,value要么全是0,要么全是1.
map.put(String.valueOf(j), x);
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println(entry.getKey() + "==" + entry.getValue());
}
}
然后使用一下2个跳表实现的集合.
跳表集合本质上的功能是一种快速查询功能,也就是说它会在一个有序的链表里面选择一些数据作为检索的种子数。用这些种子数方便进行数据的查找,非常类似于二分法。对于查询来说效率高.
ConcurrentSkipListMap
注意跳表实现的Map和加锁实现的Map的区别,使用put方法,得到的结果不一致.
@Test
public void testConcurrentSkipListMap() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Map<String, Object> map = new ConcurrentSkipListMap<>();
for (int i = 0; i < 2; i++) {
int x = i;
new Thread(() -> {
for (int j = 0; j < 10; j++) {
//同样的代码逻辑,这里使用ConcurrentSkipListMap与CurrentHashMap结果是不相同的.
//虽然都有10个值,但是value值不同.
map.put(String.valueOf(j), x);
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println(entry.getKey() + "==" + entry.getValue());
}
}
ConcurrentSkipListSet
这个使用方法和set一致
/**
* 测试skipListSet
*/
@Test
public void testConcurrentSkipListSet() {
CountDownLatch countDownLatch = new CountDownLatch(2);
Set<String> set = new ConcurrentSkipListSet<>();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
//添加的内容相同会覆盖set不可重复
//set.add("线程:"+Thread.currentThread().getName()+"-添加的内容" + j);
set.add("线程:" + "-添加的内容" + j);
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (String s : set) {
System.out.println(s);
}
}
=分隔一下================
实现一下队列.
➣ Queue队列:
➣ ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列;
➣ LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按FIFO(先进先出)排序元素;
➣ LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,
该阻塞队列同时支持FIFO和FILO两种操作方式;
➣ ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按FIFO(先进先出)排序元素。
➣ ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。
队列常用的方法
队列的常用的方法:
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
以上三个会抛出异常.
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
以上三个不会抛出异常但是会出现null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
以上两个会阻塞
ArrayBlockingQueue
该类创建时有界,因此要指定队列大小
======
junit对多线程创建不是很好,尽量使用内部类加主方法
/**
* 测试ArrayBlockingQueue,能保证消费者消费一个,生产者就生产一个
*/
class TestArrayBlockingQueue {
public static void main(String[] args) {
//创建一个公用的阻塞队列,必须数组的实现必须要指定数量
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(5);
//创建生产者线程
for (int i = 0; i < 20; i++) {
int index = i;
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
//当5个线程存入了值后,方法会阻塞,知道消费者移除一个.然后才会添加一个.
blockingQueue.put(index);
System.out.println(Thread.currentThread().getName() + "生产了一个" + index);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//创建2个消费者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (; ; ) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (blockingQueue.isEmpty()) {
System.out.println("blockingQueue空了");
break;
}
try {
//先进先出(FIFO)队列
Integer take = blockingQueue.take();
System.out.println(Thread.currentThread().getName() + "{{取出了一个}}----" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
LinkedBlockingQueue
LinkedBlockingQueue和ArrayBlockingQueue使用起来一模一样.
/**
* 测试LinkedBlockingQueue对阻塞队列的实现
* 对于Queue来说,Array和Link的实现使用基本一致.都是FIFO.
*/
class TestLinkedBlockingQueue {
public static void main(String[] args) {
//创建一个公用的阻塞队列,必须数组的实现必须要指定数量
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(5);
//创建生产者线程
for (int i = 0; i < 20; i++) {
int index = i;
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
//当5个线程存入了值后,方法会阻塞,知道消费者移除一个.然后才会添加一个.
blockingQueue.put(index);
System.out.println(Thread.currentThread().getName() + "生产了一个" + index);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//创建2个消费者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (; ; ) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (blockingQueue.isEmpty()) {
System.out.println("blockingQueue空了");
break;
}
try {
//先进先出(FIFO)队列
Integer take = blockingQueue.take();
System.out.println(Thread.currentThread().getName() + "{{取出了一个}}----" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
=========
其他的队列
PriorityBlockingQueue 可以指定优先级的(通过compare)
SynchronousQueue 只能一进一出的阻塞队列
双端队列
Deque是Queue的一种特殊情况.也实现了Queue接口.
既可以实现先进先出(FIFO),也能实现(FILO)
其实从方法上看,只是增加了对队头/队尾的操作
例如:addFirst(),addLast()等
/**
* 测试一下双端队列,
* Deque是Queue的一种特殊情况.也实现了Queue接口
*/
class TestLinkedBlockingDeque {
public static void main(String[] args) {
LinkedBlockingDeque<Integer> deque = new LinkedBlockingDeque<>(5);
//生产者在前后端放数据
for (int i = 0; i < 2; i++) {
int index = i;
new Thread(() -> {
for (int j = 1; j < 5; j++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (j % 2 == 0) {
try {
deque.putFirst(index);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "向队列头添加" + index);
} else {
try {
deque.putLast(index);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "向队列尾添加" + index);
}
}
}).start();
}
//消费者从前取数据
new Thread(()->{
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (deque.isEmpty()) {
System.out.println("队列空了");
break;
}
try {
Integer integer = deque.takeFirst();
System.out.println(Thread.currentThread().getName()+"从队头取出"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//消费者从后取数据
new Thread(()->{
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (deque.isEmpty()) {
System.out.println("队列空了");
break;
}
try {
Integer integer = deque.takeLast();
System.out.println(Thread.currentThread().getName()+"从队尾取出"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}