队列包含固定长度的队列和不固定长度的队列,先进先出。
固定长度的队列往里放数据,如果放满了还要放,阻塞式队列就会等待,直到有数据取出,空出位置后才继续放;非阻塞式队列不能等待就只能报错了。
讲Condition时提到了阻塞队列的原理,Java中已经实现了阻塞队列ArrayBlockingQueue、BlockingQueue
支持两个附加操作的Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。
BlockingQueue方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
|
抛出异常 |
特殊值 |
阻塞 |
超时 |
插入 |
add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
移除 |
remove() |
poll() |
take() |
poll(time, unit) |
检查 |
element() |
peek() |
不可用 |
不可用 |
BlockingQueue 不接受 null 元素。试图add、put 或 offer 一个null 元素时,某些实现会抛出 NullPointerException。null被用作指示 poll 操作失败的警戒值。
BlockingQueue可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告Integer.MAX_VALUE 的剩余容量。
BlockingQueue实现主要用于生产者-使用者队列,但它另外还支持Collection 接口。因此,举例来说,使用remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的Collection 操作(addAll、containsAll、retainAll和 removeAll)没有必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
E- 在此 collection 中保持的元素类型
extends AbstractQueue
一个由数组支持的有界阻塞队列。此队列按FIFO(先进先出)原则对元素进行排序。队列的头部是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。
这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性(fairness) 设置为 true 而构造的队列允许按照FIFO顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
此类及其迭代器实现了Collection 和 Iterator 接口的所有可选方法。此类是Java Collections Framework 的成员。
1、ArrayBlockingQueue(intcapacity):创建一个带有给定的(固定)容量和默认访问策略的ArrayBlockingQueue。
2、ArrayBlockingQueue(intcapacity, boolean fair):创建一个具有给定的(固定)容量和指定访问策略的ArrayBlockingQueue。
3、ArrayBlockingQueue(intcapacity, boolean fair, Collection c):创建一个具有给定的(固定)容量和指定访问策略的ArrayBlockingQueue,它最初包含给定collection 的元素,并以 collection 迭代器的遍历顺序添加元素。
1、boolean add(E e):将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。
2、voidclear():自动移除此队列中的所有元素。
3、boolean contains(Object o):如果此队列包含指定的元素,则返回true。
4、int drainTo(Collection c):移除此队列中所有可用的元素,并将它们添加到给定collection 中。
5、int drainTo(Collection c, intmaxElements):最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定collection 中。
6、Iterator
7、boolean offer(E e):将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
8、boolean offer(E e, long timeout, TimeUnit unit):将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
9、E peek():获取但不移除此队列的头;如果此队列为空,则返回 null。
10、E poll():获取并移除此队列的头,如果此队列为空,则返回 null。
11、Epoll(long timeout, TimeUnit unit):获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
12、void put(E e):将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。
13、intremainingCapacity():返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。
14、boolean remove(Object o):从此队列中移除指定元素的单个实例(如果存在)。
15、intsize():返回此队列中元素的数量。
16、Etake():获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
17、Object[]toArray():返回一个按适当顺序包含此队列中所有元素的数组。
18、
19、StringtoString():返回此 collection 的字符串表示形式。
1、阻塞队列的简单使用
public class BlockingQueueTest {
public static void main(String[] args) {
// 固定大小为3的阻塞队列
final BlockingQueue queue = new ArrayBlockingQueue(3);
for (int i = 0; i < 2; i++) {
// 往队列中放数据的队列
new Thread() {
public void run() {
while (true) {
try {
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 准备放数据!");
// 若队列满了则会阻塞
queue.put(1);
System.out.println(
Thread.currentThread().getName() + " 已经放了数据," + "队列目前有 " + queue.size() + " 个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
// 从队列中取数据的队列
new Thread() {
public void run() {
while (true) {
try {
// 将此处的睡眠时间分别改为100和1000,观察运行结果
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 准备取数据!");
// 队列为空则阻塞
queue.take();
System.out
.println(Thread.currentThread().getName() + " 已经取走数据," + "队列目前有 " + queue.size() + " 个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
2、用阻塞队列实现线程间的通信
阻塞队列的实现原理(Condition锁中有提到awaitsignal)
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueCommunication {
public static void main(String[] args) {
final Business business = new Business();
// 子线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
business.sub(i);
}
}
}).start();
// 主线程
for (int i = 1; i <= 50; i++) {
business.main(i);
}
}
static class Business {
// 每次执行之前都先在对于的队列中放值,用满队列来阻塞线程,操作完成之后,清空另一个队列,相当于一个通知
BlockingQueue queue1 = new ArrayBlockingQueue(1);
BlockingQueue queue2 = new ArrayBlockingQueue(1);
// 在每次新建对象,调用构造方法之前执行
{
try {
// 初始时 queue2 满了,此时再往 queue2 中放数据则会阻塞,只有当另一个线程将 queue2 的值取走,相当于给它一个通知
queue2.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sub(int i) {
try {
queue1.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub thread sequece of " + j + ",loop of " + i);
}
try {
queue2.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void main(int i) {
try {
queue2.put(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequece of " + j + ",loop of " + i);
}
try {
queue1.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
传统集合实现同步的问题,举一个例子:Map集合线程不同步导致的问题。
解决办法:使用同步的Map集合,使用集合工具类中的方法将不同步的集合转为同步的Collections.synchronizedMap(newMap())这个方法返回一个同步的集合:
publicstatic
return new SynchronizedMap
}
SynchronizedMap类相当于一个代理类,通过查看源代码发现:该类中的所有方法都是直接返回:原Map集合方法调用后的结果,只是将返回结果的代码放在了同步代码块中以实现同步,构造是将同步锁默认置为当前对象。
HashSet与HashMap的关系与区别:
HashSet是单列的,HashMap是双列的(键值对)
关系:HashSet内部使用的是HashMap中的键,不考虑值。
查看HashSet的源代码发现其内部就是用HashMap实现的,只是没有使用HashMap的V,只使用了它的K。
JDK1.5中提供了并发Collection:提供了设计用于多线程上下文中的Collection 实现:ConcurrentHashMap、 ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和 CopyOnWriteArraySet。当期望许多线程访问一个给定collection 时,ConcurrentHashMap 通常优于同步的HashMap,ConcurrentSkipListMap 通常优于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的 ArrayList。
ConcurrentSkipListMap
ConcurrentSkipListSet
CopyOnWriteArrayList
CopyOnWriteArraySet
它最适合于具有以下特征的应用程序:set大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。它是线程安全的。因为通常需要复制整个基础数组,所以可变操作(add、set和 remove 等等)的开销很大。迭代器不支持可变remove操作。 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
传统集合中存在的其它问题:对集合迭代时,不能对集合中的元素进行修改(添加、删除……),Java5中提供的并发集合就解决了这个问题。
CopyOnWriteArrayList使用示例:
public class User implements Cloneable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof User)) {
return false;
}
User user = (User) obj;
if (this.name.equals(user.name) && this.age == user.age) {
return true;
} else {
return false;
}
}
public int hashCode() {
return name.hashCode() + age;
}
public String toString() {
return "{name:'" + name + "',age:" + age + "}";
}
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
}
return object;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
}
public class CollectionModifyExceptionTest {
public static void main(String[] args) {
// ArraysList的迭代器,在迭代的过程中如果修改了List的结构,则会异常
// Collection users = new ArrayList();
Collection users = new CopyOnWriteArrayList();
users.add(new User("张三", 28));
users.add(new User("李四", 25));
users.add(new User("王五", 31));
Iterator itrUsers = users.iterator();
while (itrUsers.hasNext()) {
User user = itrUsers.next();
if ("张三".equals(user.getName())) {
users.remove(user);
} else {
System.out.println(user);
}
}
}
}