在JDK中提供了丰富的集合框架工具,这些工具可以有效地对数据进行处理。
10.1 集合框架结构概要
Java语言中的集合框架父接口是Iterable,里面有方法iterator(),通过此方法返回Iterator对象,以进行循环处理。
接口 Collection 是List Queue Set接口的父接口,提供了集合框架最主要,最常用的操作。
10.1.1 接口List
接口List对Collection进行了扩展,运行根据索引位置操作数据,并且内容运行重复。
接口List最常用的非并发实现类就是ArrayList,它是非线程安全的,该类实现了List接口,可以对数据以链表的形式进行组织,使数据有序排序。
类ArrayList并不是线程安全的,如果想使用线程安全的链表则可以使用Vector类。
类Vector是线程安全的,所以在多线程并发操作数据时也可以无误的处理集合中的数据。
注意:当多个线程分别调用该类的iterator()方法返回Iterator对象后,再调用remove()时会出现ConcurrentModificationException异常,也就是并不支持Iterator并发的删除,所以该类在功能上还是有缺陷。
类Vector有一个子类Stack.java,它可以实现后进先出的对象堆栈。
10.1.2 接口set
接口Set也是对Collection接口进行了扩展,它具有的默认特点是内容不允许重复,排序方式为自然排序,防止元素重复的原理是元素需要重写hashCode()和equals()方法。
10.1.3 接口Deque
接口Queue可以支持对表头的操作,而接口Deque不仅支持对表头进行操作,而且还支持对表尾进行操作,所以Deque的全称是“double ended queue”双端队列。
Deque继承自Queue
10.2 非阻塞队列
非阻塞队列的特色就是队列里面没有数据时,操作队列出现异常或返回null,不具有等待/阻塞的特色。
在JDK的并发包中,常见的非阻塞队列有:
1.ConcurrentHashMap
2.ConcurrentSkipListMap
3.ConcurrentSkipListSet
4.ConcurrentLinkedQueue
5.ConcurrentLinkedDeque
6.CopyOnWriteArrayList
7.CopyOnWriteArrayList
10.2.1 类ConcurrentHashMap的使用
1.HashMap()不是线程安全的,Hashtable是线程安全的。
但当多个线程分别调用该类的iterator()方法返回Iterator对象后,在调用remove()时会出现ConcurrentModificationException异常,也就是并不支持Iterator并发的删除。
2.类ConcurrentHashMap是支持并发操作的Map对象
Hashtable和ConcurrentHashMap都支持并发操作,它们之间有什么差异呢?其实主要的差异就是HashTable不支持在循环中remove()元素。
测试HashTable
public class MyService1 {
public static Hashtable hashtable = new Hashtable();
public MyService1() {
for(int i = 0;i<5;i++) {
hashtable.put("String"+(i+1),i+1);
}
}
}
.....................
public class ThreadA extends Thread {
private MyService1 service;
public ThreadA(MyService1 service) {
super();
this.service = service;
}
public void run() {
Iterator iterator = service.hashtable.keySet().iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
......................
public class ThreadB extends Thread {
private MyService1 service;
public ThreadB(MyService1 service) {
super();
this.service = service;
}
public void run() {
service.hashtable.put("z", "zValue");
}
}
..............................
public class Test {
public static void main(String[] args) {
MyService1 service = new MyService1();
ThreadA a = new ThreadA(service);
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ThreadB b = new ThreadB(service);
b.start();
}
}
运行结果:
String5
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.base/java.util.Hashtable$Enumerator.next(Hashtable.java:1486)
at Test/cn.yu.concurrent.ThreadA.run(ThreadA.java:14)
说明Hashtable在获得了Iterator对象后,不允许更改其结构,否则出现java.util.ConcurrentModifiationException异常。
但ConcurrentHashMap却支持这个功能。
ConcurrentHashMap不支持排序,虽然LinkedHashMap支持key的顺序性,但又不支持并发,那么如果出现这种既要求并发安全性,而又要求排序的情况就可以使用类ConcurrentSkipListMap
10.2.2 类ConcurrentSkipListMap的使用
类ConcurrentSkipListMap支持排序
10.2.3 类ConcurrentSkipListSet支持排序而且不允许重复的元素的使用
public class Userinfo implements Comparable<Userinfo> {
private int id;
private String username;
public Userinfo(int id, String username) {
super();
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public int compareTo(Userinfo u) {
if(this.getId()<u.getId()) {
return -1;
}
if(this.getId()>u.getId()) {
return 1;
}
return 0;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime*result +id;
result = prime*result+((username == null)?0:username.hashCode());
return result;
}
public boolean equals (Object obj) {
if(this == obj) return true;
if(obj == null) return false;
if(getClass() != obj.getClass()) return false;
Userinfo other = (Userinfo) obj;
if(id!=other.id) return false;
if(username == null) {
if(other.username!=null) return false;
}else if(!username.equals(other.username)) return false;
return true;
}
}
......................................
public class MyService1 {
public ConcurrentSkipListSet set = new ConcurrentSkipListSet();
public MyService1() {
Userinfo userfo1 = new Userinfo(1,"username1");
Userinfo userfo2 = new Userinfo(2,"username2");
Userinfo userfo3 = new Userinfo(3,"username3");
Userinfo userfo4 = new Userinfo(4,"username4");
Userinfo userfo5 = new Userinfo(5,"username5");
Userinfo userfo44 = new Userinfo(2,"username2");
set.add(userfo1);
set.add(userfo2);
set.add(userfo3);
set.add(userfo4);
set.add(userfo5);
set.add(userfo44);
}
}
..................................................
public class ThreadA extends Thread {
private MyService1 service;
public ThreadA(MyService1 service) {
super();
this.service = service;
}
public void run() {
while(!service.set.isEmpty()) {
Userinfo userinfo = (Userinfo)service.set.pollFirst();
System.out.println(userinfo.getId()+" "+userinfo.getUsername());
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
..............................................
public class Test {
public static void main(String[] args) {
MyService1 service = new MyService1();
ThreadA b = new ThreadA(service);
ThreadA a = new ThreadA(service);
a.start();
b.start();
}
}
运行结果:
1 username1
2 username2
3 username3
4 username4
5 username5
10.2.4 类ConcurrentLinkedQueue的使用
类ConcurrentLinkedQueue提供了并发环境的队列操作
方法poll()当没有获得数据时返回为null,如果有数据时则移除表头并返回表头。
10.2.5 类ConcurrentLinkedDeque的使用
类ConcurrentLinkedQueue仅支持对列头进行操作,而ConcurrentLinkedDeque支持对列头列尾双向操作。
10.2.6 类CopyOnWriteArrayList的使用
前面介绍过,ArrayList为非线程安全的,如果想在并发中实现线程安全,则可以使用CopyOnWriteArrayList类。
使用ArrayList
public class MyServiceA {
public static List list = new ArrayList();
}
.....................
public class ThreadA extends Thread {
private MyServiceA service;
public ThreadA(MyServiceA service) {
super();
this.service = service;
}
public void run() {
for(int i =0 ;i<100;i++) {
service.list.add("anyString");
}
}
}
...........................
public class Test {
public static void main(String[] args) throws InterruptedException {
MyServiceA service = new MyServiceA();
ThreadA[] array = new ThreadA[100];
for(int i =0;i<array.length;i++) {
array[i] = new ThreadA(service);
}
for(int i = 0;i<array.length;i++) {
array[i].start();
}
Thread.sleep(3000);
System.out.println(service.list.size());
}
}
运行结果:
(每次都不一样)
在这种情况下可以使用类CopyOnWriteArrayList作为替代。
运行结果:(正确)
10000
可以随机取得值anyString
10.2.7 类CopyOnWriteSet的使用
与CopyOnWriteArrayList配套的还有一个类叫做CopyOnWriteArraySet,它也可以解决多线程的情况下HashSet不安全的问题。
10.3 阻塞队列
在JDK中提供了若干集合工具类都具有阻塞特性,所谓的阻塞队列BlockingQueue,其实就是如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻塞进入等待状态,直到BlockingQueue添加进了元素才会被唤醒。同样,如果BlockingQueue是满的,也就是没有空余空间时,试图往队列中存放元素的操作也会被阻塞进入等待状态,直到BlockingQueue里有剩余空间才会被唤醒继续操作。
10.3.1 类ArrayBlockingQueue的使用
public class ArrayBlockingQueueTest {
public static void main(String[] args) {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
try {
queue.put(1);
queue.put(1);
queue.put(1);
System.out.println(queue.size());
queue.put(1);
System.out.println(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
3
ArrayBlockingQueue只能容纳3个元素,当添加到第四个时,被阻塞。
public class ArrayBlockingQueueTest {
public static void main(String[] args) {
ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
try {
System.out.println(System.currentTimeMillis());
System.out.println(queue.size());
queue.take();
System.out.println(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
1556547380086
0
队列中没有元素,当调用take()时,被阻塞。
10.3.2 类PriorityBlockingQueue使用
类PriorityBlockingQueue支持在并发情况下的优先级队列
public class Userinfo implements Comparable<Userinfo>{
private int id;
public Userinfo() {
super();
}
public Userinfo(int id) {
super();
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int compareTo(Userinfo o) {
if(this.id<o.getId()) {
return -1;
}
if(this.id>o.getId()) {
return 1;
}
return 0;
}
}
................................
public class Test {
public static void main(String[] args) {
PriorityBlockingQueue<Userinfo> queue = new PriorityBlockingQueue<Userinfo>();
queue.add(new Userinfo(12));
queue.add(new Userinfo(13478));
queue.add(new Userinfo(1569));
queue.add(new Userinfo(1346));
queue.add(new Userinfo(1762));
queue.add(new Userinfo(12));
System.out.println(queue.poll().getId());
System.out.println(queue.poll().getId());
System.out.println(queue.poll().getId());
System.out.println(queue.poll().getId());
System.out.println(queue.poll().getId());
System.out.println(queue.poll().getId());
System.out.println(queue.poll());
}
}
运行结果:
12
12
1346
1569
1762
13478
null
10.3.3 类LinkedBlockingQueue的使用
类LinkedBlockingQueue和ArrayBlockingQueue在功能上大体一样,只不过ArrayBlockingQueue是有界的,而LinkedBlockingQueue是无界的,当然LinkedBlockingQueue类也可以定义成是有界的,但它们两者都有阻塞特性。
10.3.4 类LinkedBlockingDeque的使用
LinkedBlockingQueue只支持对列头的操作,而LinkedBlockingDeque类提供对双端结点的操作,两者都具有阻塞特性。
10.3.5 类SynchronousQueue的使用
类SynchronousQueue为异步队列。
一种阻塞队列,其中每个插入操作必须等地啊另一个线程的对应移出操作,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能再同步队列上进行peek,因为仅在视图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则与不能插入元素;也不能迭代队列,因为其中没有任何元素可用于迭代。
public class MyService {
public static SynchronousQueue queue = new SynchronousQueue();
public void putMethod(){
String putString = "anyString"+Math.random();
System.out.println("put="+putString);
try {
queue.put(putString);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void testMethod() {
try {
System.out.println("take="+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
.........................................
public class ThreadPut extends Thread{
private MyService service;
public ThreadPut(MyService service) {
super();
this.service = service;
}
public void run() {
for(int i = 0;i<10;i++) {
service.putMethod();
}
}
}
..................................
public class ThreadTake extends Thread{
private MyService service;
public ThreadTake(MyService service) {
super();
this.service = service;
}
public void run() {
for(int i = 0;i<10;i++) {
service.testMethod();
}
}
}
.....................................
public class Test {
public static void main(String[] args) {
SynchronousQueue queue = new SynchronousQueue();
System.out.println("step1");
try {
queue.put("anyStirng");
System.out.println("step2");
System.out.println(queue.take());
System.out.println("step3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
step1
程序被阻塞了,阻塞的原因是数据并没有被其他线程移走。
public class Test {
public static void main(String[] args) {
MyService service = new MyService();
ThreadPut threadPut = new ThreadPut(service);
ThreadTake threadTake = new ThreadTake(service);
threadTake.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPut.start();
}
}
运行结果:
put=anyString0.1380231222275421
put=anyString0.3165710896572209
take=anyString0.1380231222275421
take=anyString0.3165710896572209
put=anyString0.9069492577793211
take=anyString0.9069492577793211
put=anyString0.8301544045355099
take=anyString0.8301544045355099
put=anyString0.3637104834698287
take=anyString0.3637104834698287
put=anyString0.15038718648988958
take=anyString0.15038718648988958
put=anyString0.7972032644075602
take=anyString0.7972032644075602
put=anyString0.5021117173565238
take=anyString0.5021117173565238
put=anyString0.743849245255849
put=anyString0.5599161042025386
take=anyString0.743849245255849
take=anyString0.5599161042025386
成功传输数据
10.3.6 类DelayQueue的使用
类DelayQueue提供了一种延时执行任务的队列。
public class Userinfo implements Delayed{
private long delayNanoTime;//延迟的纳秒
private String username;
public Userinfo(long delayTime,String username) {
super();
this.username =username;
TimeUnit unit = TimeUnit.SECONDS;
delayNanoTime = System.nanoTime()+unit.toNanos(delayTime);
}
public String getUsername() {
return username;
}
@Override
public int compareTo(Delayed o) {
if((this.getDelay(TimeUnit.NANOSECONDS))-o.getDelay(TimeUnit.NANOSECONDS)<0)
return -1;
if((this.getDelay(TimeUnit.NANOSECONDS))-o.getDelay(TimeUnit.NANOSECONDS)>0)
return 1;
return 0;
}
public long getDelayNanoTime() {
return delayNanoTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(delayNanoTime-System.nanoTime(),TimeUnit.NANOSECONDS);
}
}
.............................................................
public class Test {
public static void main(String[] args) throws InterruptedException {
DelayQueue<Userinfo> queue= new DelayQueue<Userinfo>();
queue.add(new Userinfo(7,"username5"));
queue.add(new Userinfo(6,"username4"));
queue.add(new Userinfo(5,"username3"));
queue.add(new Userinfo(4,"username2"));
queue.add(new Userinfo(3,"username1"));
System.out.println("当前时间"+System.currentTimeMillis());
System.out.println(queue.take().getUsername()+" "+System.currentTimeMillis());
System.out.println(queue.take().getUsername()+" "+System.currentTimeMillis());
System.out.println(queue.take().getUsername()+" "+System.currentTimeMillis());
System.out.println(queue.take().getUsername()+" "+System.currentTimeMillis());
System.out.println(queue.take().getUsername()+" "+System.currentTimeMillis());
}
}
运行结果:
当前时间1556551565872
username1 1556551568873
username2 1556551569873
username3 1556551570873
username4 1556551571771
username5 1556551572771
10.3.7 类LinkedTransferQueue的使用
类LinkedTransferQueue提供的功能与SynchronousQueue有些类似,但其具有嗅探功能,也就是可以尝试性的添加一些数据。
1.take()也具有阻塞功能
transfer(e)的使用:
1.如果当前存在一个正等待获取值的消费者线程,则把数据立即传输过去;
2.否则会将元素插入到队列的尾部,并且进入阻塞状态,直到有消费者线程取走该元素。
方法tryTransfer(e)的使用:
1.如果当前存在一个正在等待获取的消费者线程,使用tryTransfer(e)方法会立即传输数据;
2.否则,如果不存在,则返回false,并且数据不放入队列中,执行的效果是不阻塞的。
方法tryTransfer(E e,long timeout,TimeUnit unit)的使用
1.如果当前存在1个正在等待获取数据的消费者线程,则立即将数据传输给它;
2.否则将把元素插入到队列尾部,等待被消费者线程获取消费掉;
3.如果在指定的时间内元素没有被消费者线程获取,则返回false,并且将元素从队列中移除。
方法boolean hasWaitingConsumer() 判断有没有消费者在等待数据
int getWaitingConsumerCount() 取得有多少个消费者在等待数据