java多线程同步笔记——阻塞队列

一、阻塞队列

    阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列,同时,阻塞队列里面的put、take方法是被加:synchronized 同步限制,下图展示了如何通过阻塞队列来合作:

      java多线程同步笔记——阻塞队列_第1张图片

二、几种常见阻塞队列

  1、BlockingQueue (常用)
     获取元素的时候等待队列里有元素,否则阻塞 
     保存元素的时候等待队列里有空间,否则阻塞 
     用来简化生产者消费者在多线程环境下的开发 


  2、ArrayBlockingQueue (数组阻塞队列)
    FIFO、数组实现 
    有界阻塞队列,一旦指定了队列的长度,则队列的大小不能被改变 
    
在生产者消费者例子中,如果生产者生产实体放入队列超过了队列的长度,则在offer(或者put,add)的时候会被阻塞,直到队列的实体数量< 队列的   

   初始size为止。不过可以设置超时时间,超时后队列还未空出位置,则offer失败。 

    如果消费者发现队列里没有可被消费的实体时也会被阻塞,直到有实体被生产出来放入队列位置,不过可以设置等待的超时时间,超过时间后会返

    回null 

   3、DelayQueue (延迟队列)
     有界阻塞延时队列,当队列里的元素延时期未到是,通过take方法不能获取,会被阻塞,直到有元素延时到期为止 
     如: 
      1.obj 5s 延时到期 
      2.obj 6s 延时到期 
      3.obj 9s 延时到期 
    那么在take的时候,需要等待5秒钟才能获取第一个obj,再过1s后可以获取第二个obj,再过3s后可以获得第三个obj 
    这个队列可以用来处理session过期失效的场景,比如session在创建的时候设置延时到期时间为30分钟,放入延时队列里,然后通过一个线程来获       取这个队列元素,只要能被获取到的,表示已经是过期的session,被获取的session可以肯定超过30分钟了,这时对session进行失效。 

   4、LinkedBlockingQueue (链表阻塞队列)
     FIFO、Node链表结构 
     可以通过构造方法设置capacity来使得阻塞队列是有界的,也可以不设置,则为无界队列 
     其他功能类似ArrayBlockingQueue 

   5、PriorityBlockingQueue (优先级阻塞队列)
    
无界限队列,相当于PriorityQueue + BlockingQueue 

    插入的对象必须是可比较的,或者通过构造方法实现插入对象的比较器Comparator 
    
队列里的元素按Comparator comparator比较结果排序,PriorityBlockingQueue可以用来处理一些有优先级的事物。比如短信发送优先     级队列,队列里已经有某企业的100000条短信,这时候又来了一个100条紧急短信,优先级别比较高,可以通过PriorityBlockingQueue来轻松实现       这样的功能。这样这个100条可以被优先发送 

   

   6、SynchronousQueue (同步队列)
     无内部容量的阻塞队列,put必须等待take,同样take必须等待put。比较适合两个线程间的数据传递。异步转同步的场景不太适用,因为对于异步

     线程来说在处理完事务后进行put,但是必须等待put的值被取走。


三、示例(ArrayBlockingQueue)-----多线程快速搜索文件

    

/**
 * 主线程
 * @author David
 *
 */
public class MainThread implements Strategy{
    public int mSum = 0;
    public static ExecutorService mThreadPool = Executors.newCachedThreadPool();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MainThread thread = new MainThread();
        Scanner in = new Scanner(System.in);
        String directory;
        String keyword;
        File file;
        do {
			System.out.println("Enter a base directory (e.g C:\\user\\malingyi)");
			directory = in.nextLine();
			file = new File(directory);
			System.out.println("Enter keyword (e.g. malingyi)");
			keyword = in.nextLine();
		} while (!file.isDirectory() || !file.exists());
        final int FILE_QUEUE_SIZE = 16;  //阻塞队列长度
        final int SEARCH_THREADS = 100; // 最大线程数
        BlockingQueue queue = new ArrayBlockingQueue<>(FILE_QUEUE_SIZE);
        FileEnumerationTask enumerator = new FileEnumerationTask(queue, file);
        List> Results = new ArrayList<>(10);    //装填搜索任务的数组
        new Thread(enumerator).start();
        for(int i = 0 ; i <= 10; i++){
        	Integer integer = new Integer(0);
        	SearchTask searchTask = new SearchTask(thread,queue, keyword);   	
        }       
	}
    public interface Strategy {
	/**
	 * 每当一个线程计算完毕之后,调用该接口,计算包含关键字的文件数量
	 * @param num 某一个线程工作结束之后传来的统计文件数量
	 * @return 返回当前的总数,并不是完全的总数。因为有可能还有其他线程正在计算
	 */
    public int countSum(int num);  
   }
	
	
	@Override
	/**
	 * 线程搜索工作完成后,回调该函数
	 */
	public int countSum(int num) {
		// TODO Auto-generated method stub
		mSum += num;
		 System.out.println("该文件夹下,包含所求关键字的文件总数为:"+mSum);
		return mSum;
	}
}
     
/**
 * 文件枚举任务类
 * @author David
 *
 */
public class FileEnumerationTask implements Runnable {
    
	public static File DUMMY = new File("");   //虚拟文件,作为扫描的结束标志。
	private BlockingQueue mQueue;          //阻塞队列
	private File mStartingDirectory;             //起始文件夹
	/**
	 * 构造一个枚举文件的任务类,负责枚举(深度优先)起始目录下所有的文件个数。
	 * @param queue  阻塞队列,枚举出来的文件放入该队列中
	 * @param startingDirectory 用户希望查找的起始文件夹,枚举从该目录下开始。
	 */
	public FileEnumerationTask(BlockingQueue queue, File startingDirectory) {
		// TODO Auto-generated constructor stub
		mQueue = queue;
		mStartingDirectory = startingDirectory;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
        try {
			enumerate(mStartingDirectory); // 递归的遍历起始目录下以及其子目录下的所有文件
			mQueue.put(DUMMY); // 遍历完毕之后,将虚拟文件夹加入队列中,便于之后搜索的结束标记确认。
		} catch (InterruptedException e) {
			// TODO: handle exception
		}
	}
	/**
	 * 遍历检查该目录下所有的文件以及其子目录下的所有文件总数。(使用递归)
	 * @param directory 开始枚举的文件夹
	 * @throws InterruptedException 
	 */
    protected void enumerate(File directory) throws InterruptedException{
    	//先确认该文件夹非空且内部含有子文件
    	File[] files = directory.listFiles();
	   if (files != null && files.length > 0) {
		   for (File file : files) {
			  if (file.isDirectory()) {
				enumerate(file);
			  }else {
				mQueue.put(file);
			  }
		   }
	   }
	   return ;
    }
}
   
/**
 * 搜索任务类,每个搜索人物类分配一定的搜索任务(大小依据何时遇到虚拟文件从而结束)
 */
public class SearchTask implements Runnable {
    private BlockingQueue mBlockQueue;
    private String keyword;
    private int count = 0;
    private boolean isfinish = false;
    Strategy mStrategy ;
    public SearchTask(Strategy strategy,BlockingQueue blockingQueue, String keyword) {
		// TODO Auto-generated constructor stub
    	mStrategy = strategy;
    	mBlockQueue = blockingQueue;
    	this.keyword = keyword;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
        try {
		    boolean done = false;
		    while (!done) {
				File file = mBlockQueue.take(); // 取出队列的头元素
				if (file == FileEnumerationTask.DUMMY) {
					mBlockQueue.put(file);   //遇到虚拟文件夹,将其放入队列中,该线程搜索任务完毕
					                         //当队列中只剩下虚拟文件夹的时候,所有线程都将退出
					done = true;
				}else {
					if (search(file)) {
					    count++;
					}
				}
			}
		    if (done) {
				mStrategy.countSum(count);
				
			}
		} catch (FileNotFoundException e) {
			// TODO: handle exception
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	protected boolean search(File file) throws FileNotFoundException{
		try(Scanner in = new Scanner(file)){
			int lineNumber = 0;
			while (in.hasNextLine()) {
				lineNumber++;
				String line = in.nextLine();
				if (line.contains(keyword)) {
//					System.out.printf("%s:%d:%s\n",file.getPath(),lineNumber,line);
					return true;
				}
			}
			return false;
		}
	}
}
    多线程同时查询文件,可以减少查询时间,如果每个线程都加上搜索算法,那么搜索速度就会更加快速。

四、总结

    使用java自带的阻塞队列包,可以很方便的进行多线程同步的控制,不用进行加锁和唤醒的处理,程序员不需要担心线程安全问题,可以更专注

于其他方面的编程。

    

你可能感兴趣的:(多线程)