CountDownLatch

类介绍

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。 

一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行

使用场景

在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。

方法说明

countDown()方法
public void countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。
await方法
public boolean await(long timeout,TimeUnit unit) throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:

	* 由于调用 countDown() 方法,计数到达零;或者
	* 其他某个线程中断当前线程;或者
	* 已超出指定的等待时间。

如果计数到达零,则该方法返回 true 值。
如果当前线程:

	* 在进入此方法时已经设置了该线程的中断状态;或者
	* 在等待时被中断,

则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。
 
参数:
timeout - 要等待的最长时间
unit - timeout 参数的时间单位。
返回:
如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false
抛出:
InterruptedException - 如果当前线程在等待时被中断

例1:模拟100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。(十个人都到达终点时才结束)

public class CountDownLatchTest {
	public static void main(String[] args) throws InterruptedException {

		// 开始的倒数锁 从1开始倒计时 减1就到0 开始赛跑
		CountDownLatch begin = new CountDownLatch(1);
		// 结束的倒数锁 从10开始倒计时 每一个人到达终点将减1  到0时全跑完 才比赛结束
		CountDownLatch end = new CountDownLatch(10);
		// 十名选手
		ExecutorService exec = Executors.newFixedThreadPool(10);

		//for循环 代表实名选手准备就绪 begin.countDown()未执行 所有的线程都不会执行
		for (int index = 0; index < 10; index++) {
			exec.execute(new MyRunnable(index, begin,end));
		}
		System.out.println("Game Start");
		// begin减一 变为0,开始游戏
		begin.countDown();
		// 等待end变为0时,代表所有的选手都跑完
		end.await();
		System.out.println("Game Over");
		exec.shutdown();
	}
}
public  class MyRunnable implements Runnable{

	int no;//队员编号

	//同一把锁
	public static Object lock = new Object();

	//开始倒计时的计数器 从1开始倒计时 减去1到0  立即向下执行 游戏开始
	public  CountDownLatch begin;

	//结束倒计时的计数器 10个人 从10开始倒计时 每当有一个人到达终点时计数器减1 直到减到0  end.await(); 开始向下执行
	public  CountDownLatch end;

	public MyRunnable(int no, CountDownLatch begin, CountDownLatch end) {
		this.no = no;
		this.begin = begin;
		this.end = end;
	}

	@Override
	public void run() {
		try {
			// 如果当前计数为零,则此方法立即向下执行
			begin.await();
			Thread.sleep((long) (Math.random() * 10000));
			System.out.println("No." + no + " arrived");
		} catch (InterruptedException e) {
		} finally {
			// 每个选手到达终点时,end就减一
			synchronized (lock){
				end.countDown();
			}
		}
	}
}

输出结果

Game Start
No.9 arrived
No.6 arrived
No.8 arrived
No.7 arrived
No.10 arrived
No.1 arrived
No.5 arrived
No.4 arrived
No.2 arrived
No.3 arrived
Game Over

例2:根据多个图片的url地址实现多个图片的多线程上传全都上传成功后执行后续程序(但是后来发现用的Callable,在进行FutureTask.get()获取结果的时候线程阻塞,所以并不是没有实现多线程上传,但是有提供解决办法

/***根据url上传图片
   * @param urls  远程图片地址列表
   * @param fileStoryType 上传位置
   * @value FileUploadType.TEMP  上传的临时文件夹
            FileUploadType.NO_TEMP  上传到正式文件夹
            FileUploadType.GRAP_TEMP 上传到图片抓取文件夹
   *@return 返回上传的状态 里面包含原上传的url地址 页面展示的url地址等
   */
  public static List<FtpUploadState> uploadFileByUrls(List<String> urls, Short fileStoryType) {
          List<FtpUploadState> ftpUploadStateList = null;
          if(urls != null && urls.size() > 0){
              ftpUploadStateList = new ArrayList<FtpUploadState>();
              ExecutorService exec = Executors.newFixedThreadPool(urls.size());
              CountDownLatch latch = new CountDownLatch(urls.size());  //计数器的数量 上传多少图片数量就会是多少 全部上传完成才会执行下一步
              for (String romoteUrl : urls) {
                   UploadFileCallable uft = new UploadFileCallable(romoteUrl,FileUploadType.GRAP_TEMP, latch);
                   Future<FtpUploadState> ftpUploadStateFuture = exec.submit(uft); //执行线程并且返回结果
                   try {
                        ftpUploadStateList.add(ftpUploadStateFuture.get());  
//取出call线程执行完毕后的返回结果
//ftpUploadStateFuture.get() 会阻塞主线程,当线程执行完才继续主线程  所以此处多线程上传文件并不是真正的多线程 而是一个线程一个线程的执行   具体原因详看下面的示例 
//解决办法:将ftpUploadStateFuture放到list中在for循环外面去循环list去ftpUploadStateFuture.get()  而不是在for循环里面get
                   } catch (InterruptedException e) {
                        e.printStackTrace();
                   } catch (ExecutionException e) {
                        e.printStackTrace();
                   }
              }
              try {
                   latch.await();  //只要latch不为0就会一直等待 不会执行程序的下一步(所有图片上传完成后才执行下一步)
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
              exec.shutdown();
          }
          return ftpUploadStateList;
  }

 

class UploadFileCallable implements Callable {
     static final Logger logger = LoggerFactory.getLogger(UploadFileCallable.class);
     private Short fileType;
     private String filePathFormat;
     private String fileName;
     private String urlImg;
     private FtpUploadState ftpUploadState;     
     private CountDownLatch latchCount;

     public UploadFileCallable() {}
     public UploadFileCallable(String urlImg,Short fileType, CountDownLatch latchCount) {
          super();
          this.fileType = fileType;
          this.latchCount = latchCount;
          this.urlImg = urlImg;
     }
     public UploadFileCallable(Short fileType, String filePathFormat, String fileName,CountDownLatch latchCount) {
          super();
          this.fileType = fileType;
          this.filePathFormat = filePathFormat;
          this.fileName = fileName;
          this.latchCount = latchCount;
     }

      //上传文件的线程
      public FtpUploadState call() throws Exception {
           FtpUploadState ftpUploadState = null;
              int i =1;
              try {
                   while (i <= 3) {
                        ftpUploadState = UploadFileUtil.uploadFileByUrl(urlImg, fileType);
                   if(ftpUploadState.isState()){
                        break;
                   }else {
                        logger.info("第"+i+"次抓取图片,mark{}",urlImg);
                        i++;
                   }
                   }
              }catch(Exception e){
                   e.printStackTrace();
              }
              latchCount.countDown();  //每当上传完成一个图片 计数器减一 上传完成为0 主程序latch.await()出可以执行下面的代码
         return ftpUploadState;
     }

注意:下面举例说明 例2并不是多线程去上传文件,并提供相应的解决办法

方法体执行从1到4100 、4200 、4300 、4400 、4500 、4600 、4700 、4800 、4900 、5000的和 并返回值

1、单线程循环十次执行某个方法记录耗费时间

public class TestSigle {
	
	public static void main(String[] args) throws Exception {
		Long start = System.currentTimeMillis();
		for (int j = 41; j < 51; j++) {
			System.out.println("主程序循环体内************************");
			//循环执行的方法体
			int sum = 0;
			for (int i = 0; i <= j*100; i++) {
				Thread.sleep(1);
				sum += i;
			}
			System.out.println("单线程 sum:sum"+sum);
		}

		Long end = System.currentTimeMillis();
		System.out.println("耗费时间:" + String.valueOf((end - start) / 1000) + "秒");
		System.out.println("主程序最后结束------------------------------------");
	}

}



/*
 * 
输出结果:依次有序循环执行
    主程序循环体内************************
    单线程 sum:8407050

    主程序循环体内************************
    单线程 sum:8822100

    主程序循环体内************************
    单线程 sum:9247150

    主程序循环体内************************
    单线程 sum:9682200

    主程序循环体内************************
    单线程 sum:10127250

    主程序循环体内************************
    单线程 sum:10582300

    主程序循环体内************************
    单线程 sum:11047350

    主程序循环体内************************
    单线程 sum:11522400

    主程序循环体内************************
    单线程 sum:12007450

    主程序循环体内************************
    单线程 sum:12502500

耗费时间:82秒
主程序最后结束------------------------------------
 */

2、采用Callable接口结合线程池获取返回值(由于每执行一次就去获取会阻塞主线程)

public class TestCallable {

	public static void main(String[] args) throws Exception {

		ExecutorService exec = newFixedThreadPool(10);
		Long start = System.currentTimeMillis();
		for (int j = 41; j < 51; j++) {
			Future<Integer> future = exec.submit(new ThreadDemo(j*100));
			Integer sum = future.get();  //FutureTask 可用于 闭锁 单个线程不执行完主程序不往下执行
			System.out.println(sum);
			System.out.println("主程序循环体内************************");

		}

		Long end = System.currentTimeMillis();
		System.out.println("耗费时间:" + String.valueOf((end - start) / 1000) + "秒");

		exec.shutdown();
		System.out.println("主程序最后结束------------------------------------");

	}

}

class ThreadDemo implements Callable<Integer>{

	private Integer num;

	public ThreadDemo(Integer num) {
		this.num = num;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 0; i <= num; i++) {
			Thread.sleep(1);
			sum += i;
		}
		System.out.println("Callable.call() sum---->:" + sum);
		return sum;
	}

}


/**
输出结果:在for循环体内执行 FutureTask.get()阻塞了主线程将任务塞进线程池

    Callable.call() sum---->:8407050
    主程序循环体内************************

    Callable.call() sum---->:8822100
    
    主程序循环体内************************
    Callable.call() sum---->:9247150
    
    主程序循环体内************************
    Callable.call() sum---->:9682200
    
    主程序循环体内************************
    Callable.call() sum---->:10127250
    
    主程序循环体内************************
    Callable.call() sum---->:10582300
   
    主程序循环体内************************
    Callable.call() sum---->:11047350
    
    主程序循环体内************************
    Callable.call() sum---->:11522400
    
    主程序循环体内************************
    Callable.call() sum---->:12007450
    
    主程序循环体内************************
    Callable.call() sum---->:12502500
    
    主程序循环体内************************
    耗费时间:82秒
    主程序最后结束------------------------------------
*/

3、自己将Furure放进List等主线程将多个线程都塞进其他线程里再去获取遍历结果

public class TestCallable {
	
	public static void main(String[] args) throws ExecutionException, InterruptedException {

		List<Future<Integer>> list = new ArrayList<>();

		ExecutorService exec = newFixedThreadPool(10);
		Long start = System.currentTimeMillis();
		for(int j = 41;j < 51;j++){
			Future<Integer> future = exec.submit(new ThreadDemo(j*100));
			list.add(future);
			System.out.println("主程序循环体内************************");

		}

                System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");

		//将list里的FutureTask获取出来,调用get方法获取返回的值
		list.stream().forEach(future -> {
			try {
				Integer sum = future.get();
				System.out.println("future.get() sum:"+sum);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			} catch (ExecutionException e1) {
				e1.printStackTrace();
			}
		});
		Long end = System.currentTimeMillis();
		System.out.println("耗费时间(有循环遍历的时间):"+String.valueOf((end - start) / 1000)+"秒");

		exec.shutdown();
		System.out.println("主程序最后结束------------------------------------");
	}

}

class ThreadDemo implements Callable<Integer>{

	private Integer num;

	public ThreadDemo(Integer num) {
		this.num = num;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 0; i <= num; i++) {
			Thread.sleep(1);
			sum += i;
		}
		System.out.println("Callable.call() " + num +" sum---->:"+ sum);
		return sum;
	}
	
}


/**
输出结果:没有阻塞主线程将任务塞到线程池内

    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************

    Callable.call() 4200 sum---->:8822100
    Callable.call() 4300 sum---->:9247150
    Callable.call() 4400 sum---->:9682200
    Callable.call() 4500 sum---->:10127250
    Callable.call() 4600 sum---->:10582300
    Callable.call() 4100 sum---->:8407050
    future.get() sum:8407050
    future.get() sum:8822100
    future.get() sum:9247150
    future.get() sum:9682200
    future.get() sum:10127250
    future.get() sum:10582300
    Callable.call() 4700 sum---->:11047350
    future.get() sum:11047350
    Callable.call() 4800 sum---->:11522400
    future.get() sum:11522400
    Callable.call() 4900 sum---->:12007450
    future.get() sum:12007450
    Callable.call() 5000 sum---->:12502500
    future.get() sum:12502500

    耗费时间(有循环遍历的时间):9秒
    主程序最后结束------------------------------------

*/

4、利用FutureTask

       FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。

FutureTask执行多任务计算的使用场景,利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。

public class TestCallable {
	public static void main(String[] args) throws ExecutionException, InterruptedException {


		// 创建任务集合
		List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
		ExecutorService exec = newFixedThreadPool(10);

		Long start = System.currentTimeMillis();
		for (int j = 41; j < 51; j++) {
			// 传入Callable对象创建FutureTask对象
			FutureTask<Integer> ft = new FutureTask<Integer>(new ThreadDemo(j * 100));
			taskList.add(ft);
			exec.submit(ft);
			System.out.println("主程序循环体内************************");

		}
    
                System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");

		//将list里的FutureTask获取出来,调用get方法获取返回的值
		taskList.stream().forEach(future -> {
			try {
				Integer sum = future.get();
				System.out.println("future.get() sum:" + sum);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			} catch (ExecutionException e1) {
				e1.printStackTrace();
			}
		});
		Long end = System.currentTimeMillis();
		System.out.println("耗费时间(有循环遍历的时间):" + String.valueOf((end - start) / 1000) + "秒");

		exec.shutdown();
		System.out.println("主程序最后结束------------------------------------");
	}

}

class ThreadDemo implements Callable<Integer> {

	private Integer num;

	public ThreadDemo(Integer num) {
		this.num = num;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 0; i <= num; i++) {
			Thread.sleep(1);
			sum += i;
		}
		System.out.println("Callable.call() " + num + " sum---->:" + sum);
		return sum;
	}
}



/**
输出结果:没有阻塞主线程将任务塞到线程池内

    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************
    主程序循环体内************************

    Callable.call() 4200 sum---->:8822100
    Callable.call() 4300 sum---->:9247150
    Callable.call() 4400 sum---->:9682200
    Callable.call() 4500 sum---->:10127250
    Callable.call() 4600 sum---->:10582300
    Callable.call() 4100 sum---->:8407050
    future.get() sum:8407050
    future.get() sum:8822100
    future.get() sum:9247150
    future.get() sum:9682200
    future.get() sum:10127250
    future.get() sum:10582300
    Callable.call() 4700 sum---->:11047350
    future.get() sum:11047350
    Callable.call() 4800 sum---->:11522400
    future.get() sum:11522400
    Callable.call() 4900 sum---->:12007450
    future.get() sum:12007450
    Callable.call() 5000 sum---->:12502500
    future.get() sum:12502500

    耗费时间(有循环遍历的时间):9秒
    主程序最后结束------------------------------------

*/

CountDownLatch、CyclicBarrier、Semaphore共同之处与区别以及各自使用场景

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值