目录
3,有了synchronized可以同步代码,为什么还有有JUC?
4,synchronized锁与juc提供的Lock锁有什么区别?
2,ReentrantReadWriteLock (可重入读写锁)
一,概述
1,JUC是什么?
jdk提供了一些多线程同步相关的并发工具类,在java.util.concurrent包下,简称juc。
2,JUC有什么?
juc提供了原子操作类、各种锁、线程安全的集合、线程安全的队列等工具类。
3,有了synchronized可以同步代码,为什么还有有JUC?
来个小示例,模拟业务需求:有三个步骤,每个步骤一个方法,每个方法使用一个线程来负责执行,并且这三个方法是有执行顺序:
方法A由线程A来执行,方法B由线程B来执行,方法C由线程C来执行
同时三个方法的执行顺序为A->B->C
使用synchronized关键字来实现,代码如下:
package com.salulu.juc1;
public class SyncKeyDemo {
public static void main(String[] args) {
SyncResourceInfo resourceInfo = new SyncResourceInfo();
new Thread(()->{
for (int i = 0; i < 10; i++) {
resourceInfo.funA();
}
},"Thread-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
resourceInfo.funB();
}
},"Thread-B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
resourceInfo.funC();
}
},"Thread-C").start();
}
}
class SyncResourceInfo {
private int flag = 1;
public synchronized void funA() {
while (flag != 1) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" -> funA");
flag = 2;
this.notifyAll();
}
public synchronized void funB() {
while (flag != 2) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" -> funB");
flag = 3;
this.notifyAll();
}
public synchronized void funC() {
while (flag != 3) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" -> funC");
flag = 1;
this.notifyAll();
}
}
运行结果:
Thread-A -> funA
Thread-B -> funB
Thread-C -> funC
Thread-A -> funA
Thread-B -> funB
Thread-C -> funC
Thread-A -> funA
Thread-B -> funB
....
功能是实现了,但是有个缺陷,就是在方法中没有办法唤醒指定的线程,只能唤醒所有的线程。
那能不能解决这个问题呢?让线程唤醒指定的线程
下面使用JUC来实现:
package com.salulu.juc1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockSyncDemo {
public static void main(String[] args) {
ResourceInfo resourceInfo = new ResourceInfo();
new Thread(()->{
for (int i = 0; i < 10; i++) {
resourceInfo.funA();
}
},"Thread-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
resourceInfo.funB();
}
},"Thread-B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
resourceInfo.funC();
}
},"Thread-C").start();
}
}
class ResourceInfo{
private int flag = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void funA() {
try {
lock.lock();
while (flag!=1){// 如果标记不等于1则等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+" -> funA");
// 设置标记位值为2,唤醒指定的线程来执行对应的方法
flag=2;
condition2.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void funB() {
try {
lock.lock();
while (flag!=2){// 如果标记不等于2则等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+" -> funB");
// 设置标记位值为3,唤醒指定的线程来执行对应的方法
flag=3;
condition3.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void funC() {
try {
lock.lock();
while (flag!=3){// 如果标记不等于1则等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+" -> funC");
// 设置标记位值为1,唤醒指定的线程来执行对应的方法
flag=1;
condition1.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
执行结果:
Thread-A -> funA
Thread-B -> funB
Thread-C -> funC
Thread-A -> funA
Thread-B -> funB
....
使用JUC来实现,在实现功能的基础上,还做到了只唤醒指定需要唤醒的线程。
4,synchronized锁与juc提供的Lock锁有什么区别?
- synchronized无法判断获取锁的状态,Lock可以判断是否获取锁
- synchronized会自动释放锁,Lock需要手动释放
- synchronized获取不到锁时会一直等待,Lock提供了tryLock()方法来尝试获取锁,可以写获取锁失败的代码逻辑
- synchronized是非公平锁,但是Lock自己自定义锁的类型为公平锁或非公平锁
- synchronized是Java关键字,Lock是Java的接口和相关的实现类
- 共同点:都是可重入锁,都能实现同步功能
二、原子类
关于原子操作类,在之前有写过一遍笔记,并发编程四-原子操作(比较并替换CAS操作)
地址:https://blog.csdn.net/baidu_32689899/article/details/106730593
这里做个补充:带版本号的原子操作类,每次进行CAS操作都可以设置版本号,比如让版本号+1。
实现类:AtomicStampedReference<V>
可以看到是带一个泛型的,下面来看看它的构造函数:
/**
* @param initialRef 初始值(初始对象)
* @param initialStamp 版本号
*/
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
使用:
// 这是使用Integer类型,初始值为0,版本号为0
AtomicStampedReference atomicStampedReference = new AtomicStampedReference<Integer>(0,0);
// 每次进行原子更新的时候,就让版本号+1
boolean b1 = atomicStampedReference.compareAndSet(0, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
boolean b2 = atomicStampedReference.compareAndSet(100, 200,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
// 这里再次把值设置为0,出现ABA的情况,在多个线程同时进行CAS操作的时候,因为版本号不同,所以是会更新数据失败的,避免了ABA问题
boolean b3 = atomicStampedReference.compareAndSet(200, 0,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
三、锁
1,ReentrantLock (可重入锁)
ReentrantLock内部实现了公平锁和非公平锁,关于公平锁具体的实现在一篇笔记中有写到过:
并发编程五(AQS):https://blog.csdn.net/baidu_32689899/article/details/106746253
这就介绍一下它提供的API:
lock():获得锁。
tryLock():只有在调用时锁没有被其他线程持有时才获取锁。
tryLock(long timeout, TimeUnit unit):只有在调用时锁没有被其他线程持有时才获取锁。可以设置等待超时时间,如果在此时间内还没有获取锁,则抛出异常(InterruptedException)
unlock():释放锁。
newCondition():获取一个Condition对象,该对象提供了线程的等待和通知的方法,但是值得注意的是通知唤醒线程是可以指定线程的,提供了对线程更加丰富的操作。关于Condition的使用,在上面概述中有示例代码。
isLocked():查询此锁是否由任何线程持有。
getQueueLength():返回等待获取此锁的线程数的估计数。这个值只是一个估计值,因为在此方法遍历内部数据时,线程的数量可能会动态变化。
hasQueuedThread():查询给定的线程是否正在等待获取此锁。注意获取锁可能在任何时候发生。
getQueuedThreads():返回包含可能正在等待获取此锁的线程的集合。
2,ReentrantReadWriteLock (可重入读写锁)
2.1 原理介绍
首先看下构造函数:
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
可以看到,读锁和写锁,内读写锁内部其实是分别new了两个不同的锁对象。
读锁可以理解为多个线程都可以获取到该锁对象
写锁的实现其实与ReentrantLock一样
2.2 使用
代码中有详细的注释,这里就不做重复的说明:
package com.salulu.juc4;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private Map<String,String> dataMap = new HashMap<>();// 线程不安全的集合
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();// 创建一个读写锁
Lock readLock = readWriteLock.readLock();// 获取读锁
Lock writeLock = readWriteLock.writeLock();// 获取写锁
public static void main(String[] args) {
ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
// 使用6个线程来写数据
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
String str = String.valueOf(finalI);
readWriteLockDemo.writeFun(str,str);
},""+i).start();
}
// 使用6个线程来读数据
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
String str = String.valueOf(finalI);
readWriteLockDemo.readFun(str);
},""+i).start();
}
}
// 读锁:运行多个线程同时获取读锁,也就是说同时可以运行多个线程读取数据
public void readFun(String key){
readLock.lock();// 获取读锁
try{
String threadName = Thread.currentThread().getName();
System.out.println(threadName+" read key="+key+" 开始");
String val = dataMap.get(key);
System.out.println(threadName+" read key="+key+" 完成");
}finally {
readLock.unlock();// 释放读锁
}
}
// 写锁:同时只会有一个线程能获取到锁,也就是说同时只能有一个线程进行写操作
public void writeFun(String key,String value){
writeLock.lock();// 获取写锁
try{
String threadName = Thread.currentThread().getName();
System.out.println(threadName+" write key="+key+",value="+value+" 开始");
dataMap.put(key,value);
System.out.println(threadName+" write key="+key+",value="+value+" 完成");
}finally {
writeLock.unlock();// 使用写锁
}
}
}
运行结果:
四、线程间的通信(辅助工具类)
1,CountDownLatch
允许一个或多个线程等待,直到在其他线程中执行的操作完成,计数器归零后,等待线程继续运行。
示例:
package com.salulu.juc5;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 某个业务需要三个小的业务计算逻辑,都是耗时操作,等三个步骤完成后,才能继续往下执行
// 这里初始化计数器的值为3
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
try {
// 模拟业务A
Thread.sleep(1000);
System.out.println("业务A完成");
// 业务A完成后给计数器减一
countDownLatch.countDown();
// 模拟业务B
Thread.sleep(1000);
System.out.println("业务B完成");
// 业务B完成后给计数器减一
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
// 模拟业务C
Thread.sleep(1000);
System.out.println("业务C完成");
// 业务C完成后给计数器减一
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 等待三个小的业务处理完成后
countDownLatch.await();
System.out.println("完美完成。");
}
}
2,CyclicBarrier
2.1 构造函数
带两个参数的:public CyclicBarrier(int parties, Runnable barrierAction)
parties:需要满多少个线程,这些线程才继续执行
barrierAction:线程数满parties的值的时候,将执行指定的barrier动作,由最后一个进入barrier的线程执行。
一个参数的:public CyclicBarrier(int parties, Runnable barrierAction)
parties:需要满多少个线程,这些线程才继续执行
2.2 代码示例:
package com.salulu.juc5;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 某个集字的小游戏,需要同时集齐大威天龙四个字,才能打开门
CyclicBarrier cyclicBarrier = new CyclicBarrier(4,()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("集齐大威天龙,门被打开");
});
new Thread(()->{
try {
System.out.println("到达栅栏1-得到'大");
cyclicBarrier.await();
System.out.println("冲破栅栏1");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
System.out.println("到达栅栏2-得到'威'");
cyclicBarrier.await();
System.out.println("冲破栅栏2");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
System.out.println("到达栅栏3-得到'天'");
cyclicBarrier.await();
System.out.println("冲破栅栏3");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
System.out.println("到达栅栏4-得到'龙'");
cyclicBarrier.await();
System.out.println("冲破栅栏4");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
运行结果:
到达栅栏1-得到'大
到达栅栏2-得到'威'
到达栅栏3-得到'天'
到达栅栏4-得到'龙'
集齐大威天龙,门被打开
冲破栅栏4
冲破栅栏1
冲破栅栏3
冲破栅栏2
Process finished with exit code 0
2.3 使用场景:
可以用于多线程分部计算,把一个任务拆分成多个任务,在多个任务执行完成后,合并这些计算结果。
2.4 与CountDownLatch的区别;
CountDownLatch:一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CountDownLatch:多个个线程相互等待,在任何一个线程完成之前,所有的线程都必须等待。
CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
3,Semaphore
控制并发访问的线程个数
3.1 构造函数:
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
permits:可以获取许可的最初数量,即有多少个线程可以获取许可
fair:等待的线程是否使用公平锁,默认为非公平锁。
3.2 示例:
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
semaphore.acquire();
// 执行业务代码
String name = Thread.currentThread().getName();
System.out.println(name+"正在执行");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},"Thread-"+i).start();
}
}
}
运行结果描述:每次都是只有三个线程在同时执行输出语句
3.3 方法介绍:
acquire()方法:该方法是获取1个许可
acquire(int permits):带参数的,可以设置获取多个许可
release():释放许可
release(int permits):释放多个许可
4,其他
还提供了其他的一些辅助类
例如Phaser、StampedLock、Exchange等,暂不做介绍
五、线程安全集合介绍
1,Vector
Vector和ArrayList类似,它是jdk1.0时候提供的一个线程安全的集合
看源码得知它把所有可能产生线程不安全的操作方法都加了synchronized关键字来保证线程安全。
同一时刻只允许一个线程对它进行读写操作。
2,HashTable
HashTable和HashMap类似,它是jdk1.0时候提供的一个线程安全的集合
看源码得知它把所有可能产生线程不安全的操作方法都加了synchronized关键字来保证线程安全。
同一时刻只允许一个线程对它进行读写操作。
3,ConcurrentHashMap
在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响
JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率.
4,CopyOnWriteArrayList
写时复制:查询的时候,不需要加锁,随便访问,只有在写入/删除的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。这点要跟读写锁区分一下。
add()方法
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可以从代码中看到:底层维护的是一个数组,新增元素时把数组长度+1,然后把原来的数组复制到新的数组中,把新的元素放到最后一个角标位置。
删除方法也是同样的思想。
5,CopyOnWriteArraySet
与CopyOnWriteArrayList一样的套路。
看构造函数:
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
底层其实就是一个CopyOnWriteArrayList集合。
6,ConcurrentSkipListMap
底层基于链表结构,在链表的基础上加了多层索引结构。
借鉴了二分查找的思想。
特点:
可排序;
所有操作都是无阻塞的,所有操作都可以并行;
支持一些原子复合操作;
7,ConcurrentSkipListSet
构造函数:
public ConcurrentSkipListSet() {
m = new ConcurrentSkipListMap<E,Object>();
}
基于ConcurrentSkipListMap实现。
8,Collections包装方法
8.1 synchronizedList
List<String> synArrayList = Collections.synchronizedList(new ArrayList<String>());
看它是怎么实现的?怎么包装一个就可以保证线程安全呢?
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
从synchronizedList()的源码可以看出,它是根据类型判断之后,创建了一个新的集合,下面来看看这个新的集合的一些方法:
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
从上面代码可以看出,这个方法都加了synchronized关键字,来保证线程安全。
8.2 synchronizedSet
Set<E> synHashSet = Collections.synchronizedSet(new HashSet<E>());
看synchronizedSet()方法的代码:
public static <T> Set<T> synchronizedSet(Set<T> s) {
//创建了一个新的线程安全的集合
return new SynchronizedSet<>(s);
}
看SynchronizedSet类,
这这个类中没有看到ad d,remove这些方法,
但是看到了它继承SynchronizedCollection类
static class SynchronizedSet<E>
extends SynchronizedCollection<E>
implements Set<E> {
private static final long serialVersionUID = 487447009682186044L;
SynchronizedSet(Set<E> s) {
super(s);
}
SynchronizedSet(Set<E> s, Object mutex) {
super(s, mutex);
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return c.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return c.hashCode();}
}
}
来看SynchronizedCollection类,如下:
可以看到这个操作的方法调用都是加了synchronized关键字,来保证线程安全的。
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public int size() {
synchronized (mutex) {return c.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return c.isEmpty();}
}
public boolean contains(Object o) {
synchronized (mutex) {return c.contains(o);}
}
public Object[] toArray() {
synchronized (mutex) {return c.toArray();}
}
public <T> T[] toArray(T[] a) {
synchronized (mutex) {return c.toArray(a);}
}
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
public boolean containsAll(Collection<?> coll) {
synchronized (mutex) {return c.containsAll(coll);}
}
public boolean addAll(Collection<? extends E> coll) {
synchronized (mutex) {return c.addAll(coll);}
}
public boolean removeAll(Collection<?> coll) {
synchronized (mutex) {return c.removeAll(coll);}
}
public boolean retainAll(Collection<?> coll) {
synchronized (mutex) {return c.retainAll(coll);}
}
public void clear() {
synchronized (mutex) {c.clear();}
}
public String toString() {
synchronized (mutex) {return c.toString();}
}
// Override default methods in Collection
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (mutex) {c.forEach(consumer);}
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (mutex) {return c.removeIf(filter);}
}
@Override
public Spliterator<E> spliterator() {
return c.spliterator(); // Must be manually synched by user!
}
@Override
public Stream<E> stream() {
return c.stream(); // Must be manually synched by user!
}
@Override
public Stream<E> parallelStream() {
return c.parallelStream(); // Must be manually synched by user!
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}
8.3 synchronizedMap
Map<K,V> synHashMap = Collections.synchronizedMap(new HashMap<K,V>());
一样的套路,看下面的代码得知,同样是新建了一个线程同步的Map集合:
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
看SynchronizedMap类中的方法:
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();}
}
同样看到了synchronized关键字。
六、队列介绍
1,队列
队列都遵循FIFO先进先出(first in first out)
队列常用的业务场景:生产者消费者模式。
使用队列可以解偶生产者和消费者。
- ArrayBlockingQueue: 基于数组的阻塞队列
- DelayQueue: 可以实现延时任务的队列
- LinkedBlockingQueue: 基于链表的阻塞队列
- PriorityBlockingQueue: 可以实现优先级的队列
- SynchronousQueue: 同步队列,队列中只能有一个任务
2,队列的使用:
小示例:
代码中有相关说明,这里就不重复了。
package com.salulu.juc7;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) {
// 创建一个基于数组的阻塞队列
BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
// 创建一个生产者
ProductData product = new ProductData(blockingQueue);
// 创建一个消费者
ConsumteData consumte = new ConsumteData(blockingQueue);
// 用来存放生产者和消费者的数组
Thread[] threads = new Thread[20];
for (int i = 0; i < 10; i++) {
//前10个为生产者线程
threads[i] = new Thread(product);
// 后10个为消费者线程
threads[i+10] = new Thread(consumte);
}
// 启动这20个线程
for (Thread thread : threads) {
thread.start();
}
}
}
/**
* 生产者
*/
class ProductData implements Runnable{
private BlockingQueue<String> queue;
public ProductData(BlockingQueue queue){
this.queue = queue;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
// 生成数据放入队列中
String data = UUID.randomUUID().toString();
// 如果队列已满则阻塞等待
queue.put(data);
System.out.println(threadName+"生成数据:"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 消费者
*/
class ConsumteData implements Runnable{
private BlockingQueue<String> queue;
public ConsumteData(BlockingQueue queue){
this.queue = queue;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
// 从队列中获取一条数据,如果队列为空,则阻塞等待
String data = queue.take();
System.out.println(threadName+"消费数据:"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3,队列的方法:
3.1把元素(任务)加入队列的方法:
add(E e) : 添加成功返回true,失败则会抛出IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则返回 false(如果添加了时间参数,且队列已满也会阻塞)
put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞
3.2 从队列获取元素(任务)的方法:
remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若队列为空,则返回 null(如果添加了时间参数,且队列中没有数据也会阻塞)
take():获取并移除此队列头元素,若没有元素则一直阻塞。
peek() :获取但不移除此队列的头;若队列为空,则返回 null。
3.3 方法详解:
基于ArrayBlockingQueue实现类来说明:
3.3.1 put方法:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果队列满了则阻塞等待
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
put方法使用ReentrantLock锁来保证线程安全。
3.3.2 add方法:
public boolean add(E e) {
// 调用了父类AbstractQueue的add方法
return super.add(e);
}
父类AbstractQueue的add方法:
public boolean add(E e) {
// 调用offer()方法
if (offer(e))
return true;
else// 如果队列满了,offer()方法则返回false,就会进入else,然后抛出异常
throw new IllegalStateException("Queue full");
}
offer(e)方法:
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果队列满了则返回false
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
同样是使用ReentrantLock锁来保证线程安全。
3.3.3 put方法:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
3.3.4 take()方法
public E take() throws InterruptedException {
// 使用ReentrantLock锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果为空则阻塞等待
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
3.3.5 poll()方法:
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果队列为空,则返回null
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
3.3.6 peek()方法:
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 返回队列头部角标的元素
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
final E itemAt(int i) {
// 如果该角标下的元素为null,则返回null
return (E) items[i];
}