多线程编程:阻塞、并发队列的使用总结
最近,一直在跟设计的任务调度模块周旋,目前终于完成了第一阶段的调试。今天,我想借助博客园平台把最近在设计过程中,使用队列和集合的一些基础知识给大家总结一下,方便大家以后直接copy。本文都是一些没有技术含量的东西,只是做个总结,牛哥还请绕路。
老习惯,还是先跟各位纸上谈会儿兵,首先说说队列,他主要分为并发队列和阻塞队列,在多线程业务场景中使用最为普遍,我就主要结合我所做过的业务谈谈我对它们的看法,关于它们的API和官方解释就不提了。
并发队列
并发队列:最常见的业务场景就是多个线程共享同一个队列中的所有资源,就拿我们公司的业务场景来说,当用户通过多个渠道下单后,然后就会有多个不同的客户端通道同时去获取订单并处理订单,为了加快订单处理速度我们使用并发队列来充当任务源头,为了加快处理订单速度,结合多线程并发来满足需求。
并发队列没什么可说的,就是一个简单的多线程编程操作,小Demo送给各位:
1 /** 2 * 并发队列ConcurrentLinkedQueue的使用 3 */ 4 5 public class ConcurrentQueue { 6 7 public static void main(String[] args){ 8 ToyotaYQ yq = new ToyotaYQ(); 9 new Thread(yq,"ToyotaYQ_001").start(); 10 new Thread(yq,"ToyotaYQ_002").start(); 11 new Thread(yq,"ToyotaYQ_003").start(); 12 } 13 14 } 15 16 /** 17 * 任务来源 18 */ 19 class MQ{ 20 private static Queue<String> queue = null; //并发队列(线程安全) 21 22 /** 23 * 初始化并发队列 24 */ 25 public static Queue<String> initQueue(){ 26 if(queue == null){ 27 queue = new ConcurrentLinkedQueue<String>(); 28 } 29 String tasklist = "JF1GH78F18G036149,JF1SH95F6AG110830,JF1SJ94D7DG010387,JF1SH92F9CG269249,JF1SH92F5BG215090,JF1SH92F5BG222556,JF1SH92F4CG279994,JF1BR96D7CG114298,JF1BR96D0BG078632,JF1SH95F9AG094011,JF1SH98FXAG186997,JF1BM92D8BG022510,JF1BM92DXAG013855,JF1BM94D8EG036618"; 30 String[] split = tasklist.split(","); 31 List<String> task = Arrays.asList(split); //数组转集合 32 queue.addAll(task); //按照集合中元素的顺序将集合中全部元素放进队列 33 34 return queue; 35 } 36 } 37 38 /** 39 * 制单客户端 40 */ 41 class ToyotaYQ implements Runnable{ 42 43 private static final Object lock = new Object(); 44 private static Queue<String> queueYQ = MQ.initQueue(); 45 46 @Override 47 public void run() { 48 while(true){ 49 synchronized (lock){ //尽量减小锁的粒度和范围 50 String thisVIN = queueYQ.poll(); 51 if(thisVIN == null){ 52 break; 53 } 54 System.out.println(Thread.currentThread().getName() + "成功制单:" + thisVIN + "。剩余:" + queueYQ.size() + "个任务"); 55 } 56 } 57 } 58 }
阻塞队列
阻塞队列:最常见的业务场景就是生产者不断生产任务放进阻塞队列中,消费者不断从阻塞队列中获取任务;当阻塞队列中填满数据时,所有生产者端的线程自动阻塞,当阻塞队列中数据为空时,所有消费端的线程自动阻塞。这些操作BlockingQueue都已经包办了,不用我们程序员去操心了。
阻塞队列我们常用的有:LinkedBlockingQueue和ArrayBlockingQueue,它们在各方面还是很大的区别的;ArrayBlockingQueue在put,take操作使用了同一个锁,两者操作不能同时进行,而LinkedBlockingQueue使用了不同的锁,put操作和take操作可同时进行,以此来提高整个队列的并发性能。
作为开发者,使用阻塞队列需要注意的一点是:如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
阻塞队列的一些常用方法
下面是我根据这几天设计的任务调度功能模拟的一个小Demo,只不过项目中使用了MQ服务,这里用阻塞队列完成可以代替:
1 public class BlockQueueDemo { 2 3 public static void main(String[] args){ 4 BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(2); //定长为2的阻塞队列 5 //ExecutorService:真正的线程池接口 6 ExecutorService service = Executors.newCachedThreadPool(); //缓存线程池 7 //创建3个生产者: 8 ProducerDemo p1 = new ProducerDemo("车鉴定web端",queue); 9 ProducerDemo p2 = new ProducerDemo("车鉴定APP端",queue); 10 ProducerDemo p3 = new ProducerDemo("车鉴定接口端",queue); 11 ProducerDemo p4 = new ProducerDemo("车鉴定M栈",queue); 12 //创建三个消费者: 13 ConsumerDemo c1 = new ConsumerDemo("ToyotaYQ_001",queue); 14 ConsumerDemo c2 = new ConsumerDemo("ToyotaYQ_002",queue); 15 ConsumerDemo c3 = new ConsumerDemo("ToyotaYQ_003",queue); 16 17 //启动线程 18 service.execute(p1); 19 service.execute(p2); 20 service.execute(p3); 21 service.execute(p4); 22 service.execute(c1); 23 service.execute(c2); 24 service.execute(c3); 25 26 } 27 } 28 29 /** 30 * 生产者 31 */ 32 class ProducerDemo implements Runnable { 33 private String producerName; 34 private BlockingQueue queue;//阻塞队列 35 private Random r = new Random(); 36 37 //构造函数,传入生产者名称和操作的阻塞队列 38 public ProducerDemo(String producerName,BlockingQueue queue) { 39 this.producerName = producerName; 40 this.queue = queue; 41 } 42 43 @Override 44 public void run() { 45 while(true){ 46 try { 47 int task = r.nextInt(100); //产生随机数 48 System.out.println(producerName + "开始生产任务:" + task); 49 queue.put(task); //生产者向队列中放入一个随机数 50 Thread.sleep(5000); //减缓生产者生产的速度,如果队列为空,消费者就会阻塞不会进行消费直到有数据被生产出来 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 } 56 } 57 58 class ConsumerDemo implements Runnable{ 59 private String consumerName; 60 private BlockingQueue queue;//阻塞队列 61 62 //构造函数,传入消费者名称和操作的阻塞队列 63 public ConsumerDemo(String consumerName,BlockingQueue queue) { 64 this.consumerName = consumerName; 65 this.queue = queue; 66 } 67 68 @Override 69 public void run() { 70 while(true){ 71 try { 72 System.out.println(consumerName + "开始消费任务---" + queue.take());//消费者从阻塞队列中消费一个随机数 73 //Thread.sleep(500); 74 } catch (InterruptedException e) { 75 e.printStackTrace(); 76 } 77 } 78 } 79 }
开发中各位最常用最熟悉的不过也是集合了,但是前几天在设计中突然想自己控制任务的分配和修改,这就需要用到灵活操作集合中的内容了,其它也没什么,但是删除集合中的元素这一点我们还是必须要很熟练的,虽然是需要借助迭代器来删除的,但是还是记录一下吧,方便以后copy。
删除List集合中的某元素:
1 public class ListDemo { 2 3 public static void main(String[] args){ 4 ArrayList<String> arrList = new ArrayList<String>(); 5 String[] arr = {"一丰","广丰","宝马","奥迪","保时捷","沃尔沃","悍马","路虎","凯迪拉克"}; 6 arrList.addAll(Arrays.asList(arr)); //将数组转成集合 7 8 //删除前: 9 for (String thisItem:arrList){ 10 System.out.println("---"+thisItem); 11 } 12 System.out.println("#########################"); 13 14 //使用迭代器删除集合中的元素 15 Iterator it = arrList.iterator(); 16 while(it.hasNext()){ //it.hasNext()判断是否还有下一个元素 17 if("悍马".equals(it.next())){ //it.next()代表下一个元素 18 it.remove(); //【记得:remove()方法一定要调用迭代器的,不能调用List集合的】 19 } 20 } 21 22 //删除后: 23 for (String thisItem:arrList){ 24 System.out.println("---"+thisItem); 25 } 26 27 } 28 }