控制线程
什么是控制并发流程:如果不控制的情况下,那么并发的各个流程他会尽可能的跑。同时,它会收到并发控制器的控制,不受我们程序员的控制。有的时候,我们要求一些任务,它先执行,一些任务必须等我前一些任务执行完毕之后,再执行。这时候,我们必须控制它。
所以:
CountDownLatch类:
分析如下:Ta调用await()来进行等待,它要等待3,这时候,线程1,它会调用countDown()给他减一。然后,线程1继续执行。等countDown()调用三次以后。Ta线程就被唤醒了。
代码如下:
package flowcontrol.countdownlatch;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 描述 : 工厂中,质检,5个工人检查,所有人都认为通过,
* 才通过
*/
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable(){
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("No" + no + "完成了检查");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// System.out.println("countDown执行一次");
latch.countDown();
}
}
};
service.submit(runnable);
}
// 就算这里执行的晚,也会对CountDownLatch里面的值进行减小的,只要CountDownLatch中的值为0。latch.await()就不会堵塞。
// Thread.sleep(1000);
System.out.println("等待5个人检查完。。。。");
latch.await();
System.out.println("所有人都完成了工作,进入下一个环节。");
}
}
结果如下:
满足了预期。
这个优点就是五个人并行检查,不管顺序是怎么样的,谁调用的它也不关心。
比如说:多个运动员等待裁判员发号。然后,再进行起跑
或者说:当服务器进行压测的时候,需要让我们的并发尽量在同一个时间进行并发。这时候,我们会创建很多个线程,先不启动,等信号。等信号一来,再进行启动。
代码如下:
package flowcontrol.countdownlatch;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 模拟100米跑步 5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
*/
public class CountDownLatchDemo2 {
public static void main(String[] args) {
// 只要一个裁判
CountDownLatch begin = new CountDownLatch(1);
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable(){
@Override
public void run() {
System.out.println("No." + no + "准备完毕,等待发令枪");
try {
begin.await();
System.out.println("No." + no + "开始跑步了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
service.submit(runnable);
}
// 裁判员检查发令枪。。。
try {
Thread.sleep(5000);
System.out.println("发令枪响,比赛开始");
begin.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结论如下:
很显然满足预期
现在对以上的两个例子进行合并,在终点的裁判员等最后一名程序员到了,他会宣布。比赛结束。
package flowcontrol.countdownlatch;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 模拟100米跑步 5名选手都准备好了,只等裁判员一声令下,所有人同时开始跑步
* 当所有人到达终点后,比赛结束
*/
public class CountDownLatchDemo1and2 {
public static void main(String[] args) {
// 只要一个裁判
CountDownLatch begin = new CountDownLatch(1);
CountDownLatch end = new CountDownLatch(5);
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
final int no = i + 1;
Runnable runnable = new Runnable(){
@Override
public void run() {
System.out.println("No." + no + "准备完毕,等待发令枪");
try {
begin.await();
System.out.println("No." + no + "开始跑步了");
Thread.sleep(new Random().nextInt(10000));
System.out.println("No." + no + "跑到终点了");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
end.countDown();
}
}
};
service.submit(runnable);
}
// 裁判员检查发令枪。。。
try {
Thread.sleep(5000);
System.out.println("发令枪响,比赛开始");
begin.countDown();
end.await();
System.out.println("所有人到达终点");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这个例子还是挺有意思的
结论如下:
使用了一等多 和多等一的情况。
所谓的不能够重用,就是说,当你里面的数用完以后,你在调用xxx.await();是没有用的。如果要重用,就只能新建一个CountDownLatch
它三个方法是最重要的,一个是构造函数,一个是await(),一个是countDown() 一个是等待,一个是减小一个数。
Semaphore : 这个概念不仅仅在java中有,在操作系统中也有。而且,它们的意思差不多。
比如说:工厂污染只能有三个许可证,具有许可证的工厂才可以进行排污。
比如说:线程有很多,但是,通过信号量的过滤以后,就只有三个线程能获取到访问资源的权力。
在业务中的应用,比如说:我有一个业务是非常耗时的,如果多人进行访问,服务器很快就会受不了了,于是,我们就设置了一个信号量。限制同时来查询的人数。
这时候,假设我设置了三个信号量。如果,第四个线程来拿信号量。就会被堵塞,线程会进入到Blocked状态。直到其他信号量被还回来。调用release()方法。
如果说是公平的,那么就是会规规矩矩的排队,谁先来谁先获得。如果是不公平的,那么在一定情况下,就可以进行插队。
acquire(),这是可以响应中断的,
acquireUninterruptible():在获取的过程中,不响应中断。不会有异常来要求你处理。
relesae():这个是归还许可证。
代码演示:
package semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* 描述: 演示Semaphore的用法
*/
public class SemaphoreDemo {
static Semaphore semaphore = new Semaphore(3,true);
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(50);
for (int i = 0; i < 100; i++) {
service.submit(new Task());
}
service.shutdown();
}
static class Task implements Runnable{
@Override
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了许可证");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "释放了许可证");
// 记得释放信号量(许可证)
semaphore.release();
}
}
}
结果如下:
可以看到,它是三个三个拿的,先拿到执行完,在让别的线程拿到。
注意:以上面程序为例:如果拿许可证的时候是,semaphore.acquire(3);则说明它一次就拿掉三个许可证(信号)。我们会根据它的负载来确定它的信号量。注意,如果拿的时候是三个的话,释放的时候,也需要释放三个semaphore.release(3)。
Condition
candition 基本用法:
package condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 演示Condition的基本用法
*/
public class ConditionDemo1 {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
void method1() {
lock.lock();
try {
System.out.println("条件不满足,开始await");
// 这里会卡死,如果在主线程中,那么下面的内容将执行不了
condition.await();
System.out.println("条件满足了,开始执行后续的任务");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void method2(){
lock.lock();
try {
System.out.println("准备工作完成,唤醒其他的线程");
condition.signal();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo1 conditionDemo1 = new ConditionDemo1();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
// 调用这个方法,来唤醒主线程
conditionDemo1.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 方法一要在这里来执行,原因如下:如果放在上面,那么主线程卡死了,放这里的话,因为,子线程是sleep(1000)后,才开始的唤醒。
// 确保子线程可以进行唤醒工作。确保上面那个线程得到执行。
conditionDemo1.method1();
}
}
结果如下:
生产者和消费者模式,在消息队列里是非常常见的。因为,消息发送方,和消息接收方,处理速度,很可能是不一样的。
比如说:消费者发现消息已经拿空了,就让消费者休息一会。但是,它也不能一直休息吧,但生产者生产出东西以后,就会用signal去唤醒它。同时,如果队列满了,那么生产者就会休息一下。如果,队列有空闲,那么消费者就就会通知生产者去生产。
package condition;
import java.util.PriorityQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 用Condition来实现生产者和消费者模式
*/
public class ConditionDemo2 {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
ConditionDemo2 conditionDemo2 = new ConditionDemo2();
Producer producer = conditionDemo2.new Producer();
Consumer consumer = conditionDemo2.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread {
@Override
public void run(){
consume();
}
private void consume(){
while (true) {
lock.lock();
try {
while(queue.size() == 0){
System.out.println("队列空,等待数据");
// 队列空,进行等待,这里需要进行堵塞
notEmpty.await();
}
// 如果队列非空
queue.poll();
// 因为队列中,有东西被拿出来了。所以,肯定是非满的。所以,notFull条件满足,进行唤醒
notFull.signalAll();
System.out.println("从队列里取走了一个元素,队列剩余" + queue.size() + "个元素");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
class Producer extends Thread {
@Override
public void run(){
produce();
}
private void produce(){
while (true) {
lock.lock();
try {
while(queue.size() == queueSize){
System.out.println("队列满,等待空余");
// 队列空,进行等待,这里需要进行堵塞
notFull.await();
}
// 如果队列非空
queue.offer(1);
// 因为队列中,有东西被拿出来了。所以,肯定是非满的。所以,notFull条件满足,进行唤醒
notEmpty.signalAll();
System.out.println("从队列里插入了一个元素,队列剩余" + queue.size() + "个元素");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
}
结果如下:
这时候,我就感觉不应该了。按照道理来说,当队列满的时候,我取出去一个的时候,队列就应该有增加的操作。为什么这里没有呢。后来我想了一下ReentrantLock 机制是非公平锁。或许,是它在唤醒的时候,队列中10个数都增加完毕了。于是,我把他设置为公平锁。这个时候,结论就是这样了。
满足预期,但是,这样会损耗资源,不推荐;
对此,我可以让队列容量大一点,也可以模拟出混合操作。因为队列小的话,只要一等待过,等线程启动起来,人家就已经执行完十个数了。为此,我让队列中,存1000个数。这样就可以模拟出来了;
这就是模拟出来的结果。
Condition注意点
CyclicBarrier循环栅栏
代码演示:
package flowcontrol.cyclicbarrier;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 描述: 演示CyclicBarrier
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
// 这里面写的是所有人都到了之后,要去干什么。
System.out.println("所有人都到场了,大家统一出发");
}
});
for (int i = 0; i < 5; i++) {
new Thread(new Task(i, cyclicBarrier)).start();
}
}
static class Task implements Runnable{
private int id;
private CyclicBarrier cyclicBarrier;
public Task(int id, CyclicBarrier cyclicBarrier){
this.id = id;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + id + "现在前往集合地点");
try {
Thread.sleep(new Random().nextInt(1000));
System.out.println("线程" + id + "到了集合地点,开始等待其他人到达");
cyclicBarrier.await();
System.out.println("线程" + id + "出发了");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
结论如下:
符合预期
注意:循环栅栏是可以重用的,就上面程序,把5改成10,以后发现它还是可以重用。
CyclicBarrier 对于的是线程,比如说,达到五个线程await才可以执行。而CountDownLatch对应的是事件,只要倒数到0就可以发生,比如说,一个线程中它可以倒数两次。