java并发编程之并发集合框架

在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()
取得有多少个消费者在等待数据

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值