月眸


CountDownLatch的工作原理以及实例

毛毛小妖 2019-03-21 226浏览 0条评论
首页/ 正文
分享到: / / / /

CountDownLatch、CyclicBarrier是多线程重要的类,本文主要进行对其主要原理的讲解,并且通过举例的形式,使得原理更加清晰,更易了解。

1、CountDownLatch工作原理 

      CountDownLatch在多线程并发编程中充当一个计时器的功能,并且维护一个count的变量,并且其操作都是原子操作,该类主要通过countDown()和await()两个方法实现功能的,首先通过建立CountDownLatch对象,并且传入参数即为count初始值。如果一个线程调用了await()方法,那么这个线程便进入阻塞状态,并进入阻塞队列。如果一个线程调用了countDown()方法,则会使count-1;当count的值为0时,这时候阻塞队列中调用await()方法的线程便会逐个被唤醒,从而进入后续的操作。比如下面的例子就是有两个操作,一个是读操作一个是写操作,现在规定必须进行完写操作才能进行读操作。所以当最开始调用读操作时,需要用await()方法使其阻塞,当写操作结束时,则需要使count等于0。因此count的初始值可以定为写操作的记录数,这样便可以使得进行完写操作,然后进行读操作。

具体代码如下:

package concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.*;

public class CountDownLatchDemo {

	private final static CountDownLatch cdl = new CountDownLatch(3);
	private final static Vector v = new Vector();

	private static class WriteThread extends Thread {
		private final String writeThreadName;
		private final int stopTime;
		private final String str;

		public WriteThread(String name, int time, String str) {
			this.writeThreadName = name;
			this.stopTime = time;
			this.str = str;
		}

		public void run() {
			System.out.println(writeThreadName + "开始写入工作");
			try {
				Thread.sleep(stopTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			cdl.countDown();
			v.add(str);
			System.out.println(writeThreadName + "写入内容为:" + str + "。写入工作结束!");
		}
	}

	private static class ReadThread extends Thread {
		public void run() {
			System.out.println("读操作之前必须先进行写操作");
			try {
				cdl.await();// 该线程进行等待,直到countDown减到0,然后逐个苏醒过来。
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			for (int i = 0; i < v.size(); i++) {
				System.out.println("读取第" + (i + 1) + "条记录内容为:" + v.get(i));
			}
			System.out.println("读操作结束!");
		}
	}

	public static void main(String[] args) {
		new ReadThread().start();
		new WriteThread("writeThread1", 1000, "多线程知识点").start();
		new WriteThread("writeThread2", 2000, "多线程CountDownLatch的知识点").start();
		new WriteThread("writeThread3", 3000, "多线程中控制顺序可以使用CountDownLatch").start();

	}
}

运行代码,结果如下:

读操作之前必须先进行写操作
writeThread1开始写入工作
writeThread2开始写入工作
writeThread3开始写入工作
writeThread1写入内容为:多线程知识点。写入工作结束!
writeThread2写入内容为:多线程CountDownLatch的知识点。写入工作结束!
writeThread3写入内容为:多线程中控制顺序可以使用CountDownLatch。写入工作结束!
读取第1条记录内容为:多线程知识点
读取第2条记录内容为:多线程CountDownLatch的知识点
读取第3条记录内容为:多线程中控制顺序可以使用CountDownLatch
读操作结束!

从以上过程可以看出,可以使得先进行写操作然后进行读操作。

2、通过join实现CountDownLatch功能

其实上述CountDownLatch这种功能可以通过Thread对象的join方法实现同样的功能,只是这里无须调用await()方法和countDown()方法,而是使用sleep()进行控制时间,然后将读操作以及写操作通过在主线程通过join()方法使其加入主线程,使其实现只有进行写操作结束,才能进行读操作。具体代码如下所示:

package concurrent;

import java.util.Vector;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class JoinDemo {
	private final static Vector v = new Vector();
	Lock lock = new ReentrantLock();
	final Condition condition = lock.newCondition();// 创建condition对象

	private static class WriteThread extends Thread {
		private final String writeThreadName;
		private final int stopTime;
		private final String str;
		Lock lock = new ReentrantLock();
		final Condition condition = lock.newCondition();// 创建condition对象

		public WriteThread(String name, int time, String str) {
			this.writeThreadName = name;
			this.stopTime = time;
			this.str = str;
		}

		public void run() {
			System.out.println(writeThreadName + "开始写入工作");
			try {
				Thread.sleep(stopTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			v.add(str);
			System.out.println(writeThreadName + "写入内容为:" + str + "。写入工作结束!");
		}
	}

	private static class ReadThread extends Thread {
		Lock lock = new ReentrantLock();
		final Condition condition = lock.newCondition();// 创建condition对象

		public void run() {
			System.out.println("读操作之前必须先进行写操作");
			try {
				Thread.sleep(10000);// 该线程进行暂停,时间控制在写操作结束才使线程苏醒过来。
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			for (int i = 0; i < v.size(); i++) {
				System.out.println("读取第" + (i + 1) + "条记录内容为:" + v.get(i));
			}
			System.out.println("读操作结束!");
		}
	}

	public static void main(String[] args) {
		ReadThread readThread = new ReadThread();
		readThread.start();
		long start = System.currentTimeMillis();
		Thread[] write = new Thread[3];
		String[] str = { "多线程知识点", "多线程CountDownLatch的知识点", "多线程中控制顺序可以使用CountDownLatch" };

		for (int i = 0; i < 3; i++) {
			Thread t1 = new WriteThread("writeThread" + (i + 1), 1000 * (i + 1), str[i]);

			t1.start();
			write[i] = t1;
		}

		try {
			readThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 等待线程结束
		for (Thread t : write) {
			try {
				t.join();

			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}
}

运行上述程序,可以得到如下结果:

读操作之前必须先进行写操作
writeThread1开始写入工作
writeThread2开始写入工作
writeThread3开始写入工作
writeThread1写入内容为:多线程知识点。写入工作结束!
writeThread2写入内容为:多线程CountDownLatch的知识点。写入工作结束!
writeThread3写入内容为:多线程中控制顺序可以使用CountDownLatch。写入工作结束!
读取第1条记录内容为:多线程知识点
读取第2条记录内容为:多线程CountDownLatch的知识点
读取第3条记录内容为:多线程中控制顺序可以使用CountDownLatch
读操作结束!

3、线程池问题

通过上述的比较,可以通过join方法实现CountDownLatch的按顺序执行线程的功能,但是CountDownLatch有join实现不了的情况,比如使用线程池时,线程池的线程不能直接使用,所以只能使用CountDownLatch实现按顺序执行线程,而无法使用join()方法。具体代码如下:

package concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import java.util.*;

public class CountDownLatchDemo {

	private final static CountDownLatch cdl = new CountDownLatch(3);
	private final static Vector v = new Vector();
	private final static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS,
			new LinkedBlockingQueue<Runnable>());// 使用线程池

	private static class WriteThread extends Thread {
		private final String writeThreadName;
		private final int stopTime;
		private final String str;

		public WriteThread(String name, int time, String str) {
			this.writeThreadName = name;
			this.stopTime = time;
			this.str = str;
		}

		public void run() {
			System.out.println(writeThreadName + "开始写入工作");
			try {
				Thread.sleep(stopTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			cdl.countDown();
			v.add(str);
			System.out.println(writeThreadName + "写入内容为:" + str + "。写入工作结束!");
		}
	}

	private static class ReadThread extends Thread {
		public void run() {
			System.out.println("读操作之前必须先进行写操作");
			try {
				cdl.await();// 该线程进行等待,直到countDown减到0,然后逐个苏醒过来。
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			for (int i = 0; i < v.size(); i++) {
				System.out.println("读取第" + (i + 1) + "条记录内容为:" + v.get(i));
			}
			System.out.println("读操作结束!");
		}
	}

	public static void main(String[] args) {
		Thread read = new ReadThread();
		threadPool.execute(read);
		String[] str = { "多线程知识点", "多线程CountDownLatch的知识点", "多线程中控制顺序可以使用CountDownLatch" };

		for (int i = 0; i < 3; i++) {
			Thread t1 = new WriteThread("writeThread" + (i + 1), 1000 * (i + 1), str[i]);
			threadPool.execute(t1);
		}
	}

}

运行如上程序,得到以下结果:

读操作之前必须先进行写操作
writeThread1开始写入工作
writeThread2开始写入工作
writeThread3开始写入工作
writeThread1写入内容为:多线程知识点。写入工作结束!
writeThread2写入内容为:多线程CountDownLatch的知识点。写入工作结束!
writeThread3写入内容为:多线程中控制顺序可以使用CountDownLatch。写入工作结束!
读取第1条记录内容为:多线程知识点
读取第2条记录内容为:多线程CountDownLatch的知识点
读取第3条记录内容为:多线程中控制顺序可以使用CountDownLatch
读操作结束!

4、总结

CountDownLatch类主要是用来实现线程的按顺序执行。主要通过count计数器来实现功能,CountDownLatch(int count)构造函数用于初始化同步计时器,只有当同步计时器为0,主线程才会向下执行。而实现按顺序执行的两个主要方法便是await()和countDown(),其中await()使暂时不想让它执行的线程加入队列进入阻塞状态,而countDown()则是每当执行完一个可执行线程便会减1,直到count为0,那么阻塞队列的线程便会被唤醒。

        Thread对象的join方法可以实现相同的功能,但是特别地,当使用了线程池时,则join()方法便无法实现。但CountDownLatch依然可以实现功能。

            CountDownLatch类主要使用的场景有明显的顺序要求:比如只有等跑完步才能计算排名,只有等所有记录都写入才能进行统计工作等等,因此CountDownLatch完善的是某种逻辑上的功能,使得线程按照正确的逻辑进行。
---------------------
作者:carson0408 
来源:CSDN 
原文:https://blog.csdn.net/carson0408/article/details/79469725 

最后修改:2019-03-21 16:03:06 © 著作权归作者所有
如果觉得我的文章对你有用,请随意赞赏
扫一扫支付

上一篇

发表评论

说点什么吧~

评论列表

还没有人评论哦~赶快抢占沙发吧~