一、java.util.concurrent 包下的类分类图,了解一下。
一:collections部分
- 1、BlockingQueue为接口,实现该接口的子类有:
实现子类 | 类型 |
---|---|
ArrayBlockingQueue | 实现类 |
DelayQueue | 实现类 |
LinkedBlockingQueue | 实现类 |
SynchronousQueue | 实现类 |
PriorityBlockingQueue | 实现类 |
TransferQueue | 接口 |
-
2、BlockingQueue 用法,BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景
-
3、BlockingQueue (API用法)
- 如果队列到达了容纳点,例如,队列已经满了,再去put的时候,会遇到阻塞的情况,如果队列为空,如果take的时候,则会遇到阻塞的情况。直到满足条件。
- 抛异常:如果试图的操作无法立即执行,抛一个异常。
- 特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。。
- 阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
- 超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
- 如果队列到达了容纳点,例如,队列已经满了,再去put的时候,会遇到阻塞的情况,如果队列为空,如果take的时候,则会遇到阻塞的情况。直到满足条件。
-
4、BlockingQueue 代码案例(ArrayBlockingQueue 用法)
- 无法向一个 BlockingQueue 中插入 null
- ArrayBlockingQueue 类实现了 BlockingQueue 接口。
- ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。它有一个同一时间能够存储元素数量的上限。
- 你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了(译者注:因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改)。
- ArrayBlockingQueue 内部以 FIFO(先进先出)的顺序对元素进行存储。队列中的头元素在所有元素之中是放入时间最久的那个,而尾元素则是最短的那个。
public static void main(String[] args) throws Throwable {
BlockingQueue queue = new ArrayBlockingQueue(1024);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
new Thread(producer).start();
new Thread(consumer).start();
Thread.sleep(4000);
}
public static class Producer implements Runnable{
protected BlockingQueue queue = null;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
Thread.sleep(7000);
queue.put("1"); //无需考虑安全问题 直接使用
Thread.sleep(1000);
queue.put("2");
Thread.sleep(1000);
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class Consumer implements Runnable{
protected BlockingQueue queue = null;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
-
5、BlockingQueue 代码案例(延迟队列 DelayQueue 用法)
- 是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。
- Delayed,一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序。
- 代码案例:(需要注意的是:compareTo的排序规则很重要,队列会按照排序的顺序去获取数据,如果说排序的规则和到期时间的规则不一致,则会导致队首的元素还没到期,但是队内的元素已经到期了,但是队内的元素拿不出来。直达队首的元素到期能获取到之后,对内的元素才会释放)
>>>>>>>第一消息实体
class Message implements Delayed {
private int id;
private String body; // 消息内容
private long excuteTime;// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。
public int getId() {
return id;
}
public String getBody() {
return body;
}
public long getExcuteTime() {
return excuteTime;
}
public Message(int id, String body, long delayTime) {
this.id = id;
this.body = body;
this.excuteTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime();
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", body='" + body + '\'' +
", excuteTime=" + excuteTime +
'}';
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.excuteTime - System.nanoTime(), TimeUnit.NANOSECONDS);
}
@Override
public int compareTo(Delayed delayed) {
Message msg = (Message) delayed;
return Long.valueOf(this.excuteTime) > Long.valueOf(msg.excuteTime) ? 1
: (Long.valueOf(this.excuteTime) < Long.valueOf(msg.excuteTime) ? -1 : 0);
}
}
>>>>>>>第二消费消息的消费者
class Consumer implements Runnable {
// 延时队列 ,消费者从其中获取消息进行消费
private DelayQueue<Message> queue;
public Consumer(DelayQueue<Message> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Message take = queue.take();
System.out.println("消费消息id:" + take.getId() + " 消息体:" + take.getBody());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
>>>>>>第三存放消息的延时队列
public static void main(String[] args) throws Throwable {
// 创建延时队列
DelayQueue<Message> queue = new DelayQueue<Message>();
// 添加延时消息,m1 延时3s
Message m2 = new Message(2, "hello", 10000);
Message m1 = new Message(1, "world", 5000);
// 添加延时消息,m2 延时10s
//将延时消息放到延时队列中
queue.offer(m2);
queue.offer(m1);
System.out.println(queue.peek().toString());
// 启动消费线程 消费添加到延时队列中的消息,前提是任务到了延期时间
ExecutorService exec = Executors.newFixedThreadPool(1);
exec.execute(new Consumer(queue));
exec.shutdown();
//控制台输出
// Message{id=1, body='world', excuteTime=2389652621500816}
// 消费消息id:1 消息体:world
// 消费消息id:2 消息体:hello
}
- 6、 BlockingQueue 代码案例( LinkedBlockingQueue用法)
class LinkedBlockingQueueTest {
//LinkedBlockingQueue<String> queue=new LinkedBlockingQueue<String>(3);
private int i;
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(3);
class Producer implements Runnable {
@Override
public void run() {
while (true) {
try {
queue.put(i++);
//System.out.print("生产,,,,,,");
System.out.println("生产,,,,,,剩余容量:" + queue.remainingCapacity());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
try {
// Thread.sleep(1000);
queue.take();
System.out.println("消费,,,,剩余容量:" + queue.remainingCapacity());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
LinkedBlockingQueueTest linkedBlockingQueueTest=new LinkedBlockingQueueTest();
new Thread(linkedBlockingQueueTest.new Producer()).start();
new Thread(linkedBlockingQueueTest.new Consumer()).start();
- 7、 BlockingQueue 代码案例( SynchronousQueue用法)
- 一:不像ArrayBlockingQueue或LinkedListBlockingQueue,SynchronousQueue内部并没有 数据缓存空间,你不能调用peek()方法来看队列中是否有数据元素,因为数据元素只有当你试着取走的时候才可能存在,不取走而只想偷窥一下是不行的,当然遍历这个队列的操作也是不允许的。队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。数据是在配对的生产者和消费者线程之间直接传递的,并不会将数据缓冲数据到队列中。可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。
- 不能在同步队列上进行 peek,因为仅在试图要取得元素时,该元素才存在;
- **除非另一个线程试图移除某个元素,否则也不能(使用任何方法)添加元素;也不能迭代队列,因为其中没有元素可用于迭代。队列的头是尝试添加到队列中的首个已排队线程元素; 如果没有已排队线程,则不添加元素并且头为 null。 **
- 对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空集合。此队列不允许 null 元素。
- 它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
- 对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。 但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。 公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。
- SynchronousQueue的以下方法:
- iterator() 永远返回空,因为里面没东西
- peek() 永远返回null
- ** put() 往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走**
- offer() 往queue里放一个element后立即返回,如果碰巧这个element被另一个thread取走了,offer方法返回true,认为offer成功;否则返回false.
- offer(2000, TimeUnit.SECONDS) 往queue里放一个element但是等待指定的时间后才返回,返回的逻辑和offer()方法一样
- ** take() 取出并且remove掉queue里的element(认为是在queue里的。。。),取不到东西他会一直等**
- poll() 取出并且remove掉queue里的element(认为是在queue里的。。。),只有到碰巧另外一个线程正在往queue里offer数据或者put数据的时候,该方法才会取到东西。否则立即返回null
- ** poll(2000, TimeUnit.SECONDS) 等待指定的时间然后取出并且remove掉queue里的element,其实就是再等其他的thread来往里塞**
- isEmpty()永远是true
- remainingCapacity() 永远是0
- remove()和removeAll() 永远是false
- 三:代码使用案列:
SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();
Thread putThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("put thread start");
try {
queue.put(1);
} catch (InterruptedException e) {
}
System.out.println("put thread end");
}
});
Thread takeThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("take thread start");
try {
System.out.println("take from putThread: " + queue.take());
} catch (InterruptedException e) {
}
System.out.println("take thread end");
}
});
takeThread.start();
Thread.sleep(1000);
putThread.start();
//控制台输出
take thread start
put thread start
take from putThread: 1
take thread end
put thread end
-
8、 BlockingQueue 代码案例( PriorityBlockingQueue用法)
- 这是一个无界有序的阻塞队列,排序规则和之前介绍的PriorityQueue一致,只是增加了阻塞操作。同样的该队列不支持插入null元素,同时不支持插入非comparable的对象。它的迭代器并不保证队列保持任何特定的顺序,如果想要顺序遍历,考虑使用Arrays.sort(pq.toArray())。该类不保证同等优先级的元素顺序,如果你想要强制顺序,就需要考虑自定义顺序或者是Comparator使用第二个比较属性。
- DEFAULT_INITIAL_CAPACITY:默认队列容量11
- MAX_ARRAY_SIZE:最大可分配队列容量Integer.MAX_VALUE - 8,减8是因为有的VM实现在数组头有些内容
- queue:队列元素数组。平衡二叉堆实现,父节点下标是n,左节点则是2n+1,右节点是2n+2。最小的元素在最前面
- size:当前队列中元素的个数
- comparator:决定队列中元素先后顺序的比较器
- lock:所有public方法的锁
- notEmpty:队列为空时的阻塞条件
- allocationSpinLock:扩容数组分配资源时的自旋锁,CAS需要
- q:PriorityQueue只用于序列化的时候,为了兼容之前的版本。只有在序列化和反序列化的时候不为null。
-
9、BlockingQueue 代码案例( LinkedTransferQueue用法)
- LinkedTransferQueue是 SynchronousQueue 和 LinkedBlockingQueue 的合体,性能比 LinkedBlockingQueue 更高(没有锁操作),比 SynchronousQueue能存储更多的元素。当 put 时,如果有等待的线程,就直接将元素 “交给” 等待者, 否则直接进入队列。
put和 transfer 方法的区别是,put 是立即返回的, transfer 是阻塞等待消费者拿到数据才返回。transfer方法和 SynchronousQueue的 put 方法类似。
- LinkedTransferQueue是 SynchronousQueue 和 LinkedBlockingQueue 的合体,性能比 LinkedBlockingQueue 更高(没有锁操作),比 SynchronousQueue能存储更多的元素。当 put 时,如果有等待的线程,就直接将元素 “交给” 等待者, 否则直接进入队列。
public interface TransferQueue<E> extends BlockingQueue<E> {
// 如果可能,立即将元素转移给等待的消费者。
// 更确切地说,如果存在消费者已经等待接收它(在 take 或 timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则返回 false。
boolean tryTransfer(E e);
// 将元素转移给消费者,如果需要的话等待。
// 更准确地说,如果存在一个消费者已经等待接收它(在 take 或timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则等待直到元素由消费者接收。
void transfer(E e) throws InterruptedException;
// 上面方法的基础上设置超时时间
boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;
// 如果至少有一位消费者在等待,则返回 true
boolean hasWaitingConsumer();
// 返回等待消费者人数的估计值
int getWaitingConsumerCount();
}
ref: https://blog.csdn.net/wbwjx:
ref: https://www.cnblogs.com/gxyandwmm/p/9398934.html
ref: https://www.cnblogs.com/shamo89/p/7055039.html