首页 > 其他分享 >并发控制流程

并发控制流程

时间:2024-03-01 10:00:10浏览次数:15  
标签:控制 流程 System 并发 线程 println new public out

控制并发流程

  • 控制并发流程的工具类,作用就是更容易得让线程之间合作
  • 让线程之间相互配合,来满足业务逻辑
  • 比如让线程A等待线程B执行完毕后再执行等合作策略

作用

说明

Semaphore

信号量,可以通过控制“许
可证”的数量,来保证线程
之间的配合

线程只有在拿到“许可证”后
才能继续运行。相比于其
他的同步器,更灵活

CyclicBarrier

线程会等待,直到足够多
线程达到了事先规定的数
目。 一旦达到触发条件,
就可以进行下一步的动作。

适用于线程之间相互等待
处理结果就绪的场景

Phaser

和CyclicBarrier类似,但是
计数可变

Java 7加入的

CountDownLatch

和CyclicBarrier类似,数量
递减到0时,触发动作

不可重复使用

Exchanger

让两个线程在合适时交换
对象

适用场景:当两个线程工
作在同一个类的不同实例
上时,用于交换数据

Condition

可以控制线程的“等待”和
“ 唤 醒 ”

是Object.wait()的升级版

CountDownLatch

数量递减到0,触发动作。latch:门闩

  • 流程:倒数结束之前,一直处于等待状态,直到倒计时结束了,此线程才继续工作。
  • CountDownLatch(int count):仅有一个构造函数,参数count为需要倒数的数值。
  • await():调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行。
  • countDown():将count值减1,直到为0时,等待的线程会被唤起。

用法一:一个线程等待多个线程都执行完毕,再继续自己的工作。

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((long) (Math.random() * 10000));
System.out.println("No." + no + "完成了检查。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
};
service.submit(runnable);
}
System.out.println("等待5个人检查完.....");
latch.await();
System.out.println("所有人都完成了工作,进入下一个环节。");
}
}

用法二:多个线程等待某一个线程的信号,同时开始执行。

public class CountDownLatchDemo2 {

public static void main(String[] args) throws InterruptedException {
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);
}
//裁判员检查发令枪...
Thread.sleep(5000);
System.out.println("发令枪响,比赛开始!");
begin.countDown();
}
}

  • 扩展用法:多个线程等多个线程完成执行后,再同时执行
  • CountDownLatch是不能够重用的,如果需要重新计数,可以考虑使用CyclicBarrier或者创建新的CountDownLatch实例。

Semaphore信号量

  • Semaphore可以用来限制或管理数量有限的资源的使用情况。
  • 信号量的作用是维护一个“许可证”的计数,线程可以“获取”许可证,那信号量剩余的许可证就减一,线程也可以“释放”一个许可证,那信号量剩余的许可证就加一,当信号量所拥有的许可证数量为0,那么下一个还想要获取许可证的线程,就需要等待,直到有另外的线程释放了许可证

信号量使用流程

1初始化Semaphore并指定许可证的数量

2.在需要被现在的代码前加acquire()或者acquireUninterruptibly()方法

3.在任务执行结束后,调用release()来释放许可证

信号量主要方法

  • new Semaphore(int permits,boolean fair):这里可以设置是否要使用公平策略,如果传入true,那么Semaphore会把之前等待的线程放到FIFO的队列里,以便于当有了新的许可证可以分发给之前等了最长时间的线程。
  • tryAcquire():看看现在有没有空闲的许可证,如果有的话就获取,如果没有的话也没关系,我不必陷入阻塞,我可以去做别的事,过一会再来查看许可证的空闲情况。
  • tryAcquire(timeout):和tryAcquire()一样,但是多了一个超时时间,比如“在3秒内获取不到许可证,我就去做别的事”。

特殊用法

  • 一次性获取或释放多个许可证

比如TaskA会调用很消耗资源的method1(),而TaskB调用的是不太消耗资源的method2(),假设我们一共有5个许可证。那么我们就可以要求TaskA获取5个许可证才能执行,一个许可证就能执行,这样就避免而TaskB只需要获取到

了A和B同时运行的情况,我们可以根据自己的需求合理分配资源。

注意

  1. 获取和释放的许可证数量必须一致,否则比如每次都获取2个但是只释放1个甚至不释放,随着时间的推移,到最后许可证数量不够用,会导致程序卡死。(虽然信号量类并不对是否和获取的数量做规定,但是这是我们的编程规范,否则容易出错)
  2. 注意在初始化Semaphore的时候设置公平性,一般设置》为true会更合理
  3. 并不是必须由获取许可证的线程释放那个许可证,事实上,获取和释放许可证对线程并无要求,也许是A获取了,然后由B释放,只要逻辑合理即可。
  4. 信号量的作用,除了控制临界区最多同时有N个线程访问外,另一个作用是可以实现“条件等待”,例如线程1需要在线程2完成准备工作后才能开始工作,那么就线程lacquire(),而线这样的话,相当于是轻量级的程2完成任务后release(),CountDownLatch。

public class SemaphoreDemo {

static Semaphore semaphore = new Semaphore(5, 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(3);
} 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(2);
}
}
}

Condition接口(条件对象)

  • 当线程1需要等待某个条件的时候,它就去执行condition.await()方法,一旦执行了await()方法,线程就会进入阻塞状态
  • 然后通常会有另外一个线程,假设是线程2,去执行对应的条件,直到这个条件达成的时候,线程2就会去执行condition.signal()方法,这时JVM就会从被阻塞的线程里找,找到那些等待该condition的线程,当线程1就会收到可执行信号的时候,它的线程状态就会变成Runnable可执行状态

signalAll()和signal()区别

  • signalAll()会唤起所有的正在等待的线程
  • 但是signal()是公平的,只会唤起那个等待时间最长的线程

public class ConditionDemo1 {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

void method1() throws InterruptedException {
lock.lock();
try{
System.out.println("条件不满足,开始await");
condition.await();
System.out.println("条件满足了,开始执行后续的任务");
}finally {
lock.unlock();
}
}

void method2() {
lock.lock();
try{
System.out.println("准备工作完成,唤醒其他的线程");
condition.signal();
}finally {
lock.unlock();
}
}

public static void main(String[] args) throws InterruptedException {
ConditionDemo1 conditionDemo1 = new ConditionDemo1();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
conditionDemo1.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
conditionDemo1.method1();
}
}

实现生产模式

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("队列空,等待数据");
try {
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll();
notFull.signalAll();
System.out.println("从队列里取走了一个数据,队列剩余" + queue.size() + "个元素");
} 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("队列满,等待有空余");
try {
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1);
notEmpty.signalAll();
System.out.println("向队列插入了一个元素,队列剩余空间" + (queueSize - queue.size()));
} finally {
lock.unlock();
}
}
}
}
}

CyclicBarrier(循环栅栏)

  • CyclicBarrier循环栅栏和CountDownLatch很类似,都能阻塞一组线程
  • 当有大量线程相互配合,分别计算不同任务,并且需要最后统一汇总的时候,我们可以使用CyclicBarrier。CyclicBarrier可以构造一个集结点,当某一个一个线程执行完毕,它就会到集结点等待,直到所有线程都到'了集结点,那么该栅栏就被撤销,所有线程再统一出发,继续执行剩下的任务。
  • 与CountDownLatch不同
  • 作用不同:CyclicBarrier要等固定数量的线程都到达了栅栏位置才能继续执行,而CountDownLatch只需等待数字到0,也就是说,CountDownLatch用于事件,但是CyclicBarrier是用于线程的。
  • 可重用性不同:CountDownLatch在倒数到0并触发门闩打开后,就不能再次使用了,除非新建新的实例;而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 < 10; 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((long) (Math.random()*10000));
System.out.println("线程"+id+"到了集合地点,开始等待其他人到达");
cyclicBarrier.await();
System.out.println("线程"+id+"出发了");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}

标签:控制,流程,System,并发,线程,println,new,public,out
From: https://www.cnblogs.com/wangzhilei-src/p/18046316

相关文章

  • go: 协程生命周期控制
    go:协程生命周期控制原创 王义杰 运维开发王义杰 2024-02-2822:27 广东 听全文在go开发中,如果我们希望在一定条件下,比如执行了某些任务后,主动结束其它相关的协程,可以通过使用context包来实现。context包允许我们发送取消信号给一个或多个协程,这样我们就可以控制协......
  • lua协程 - 把回调模式的代码流程改成顺序执行流程
    像ugui的点击按钮,然后触发相关逻辑,都是回调模式的写法,比如下面代码的ShowAlert调用publicclassTest7:MonoBehaviour{publicGameObjectm_Alert;publicTextm_TxtMsg;publicButtonm_BtnYes;publicButtonm_BtnNo;privateLuaEnvm_LuaEnv;......
  • 亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证
    前言:只要使用到缓存,无论是本地缓存还是使用Redis做缓存,那么就会存在数据同步不一致的问题。先读取缓存,缓存数据有,则立即返回结果如果缓存中没有数据,则从数据库中读取数据把读取到的数据同步到缓存中,提供下次读请求返回数据这样的作法是大多数人使用缓存的方式,这样能......
  • 控制方法
    第四章主要讲述了指导我们改造世界的方法之一,其中包括了控制论史,什么是控制和控制系统,控制方式,控制系统的基本属性,控制过程,控制论的研究方法六个章节。其中我学到了一些概念。控制系统的基本属性:目的性、振荡性(类型:单调发散、等幅振荡【振动幅度相同】、振荡发散【原本影响很小逐......
  • 第四章 控制方法
    在深入阅读了《系统科学方法概论》的第四章后,我对控制方法有了更为深刻的认识。首先,通过阅读第一节控制论史,我对控制论的发展历程有了清晰的认识。从古代的简单控制实践到现代的复杂系统控制,控制论逐渐发展成为一门独立的学科,为现代工程、经济、社会等领域提供了重要的理论支持。......
  • Java流程控制05:Switch选择结构
    Switch多选择结构1.多选择结构还有一个实现方式就是switchcase语句2. switchcase语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。3.switch语句中的变量类型可以是:3.1byte、short、int或者char3.2从JavaSE7开始......
  • 《系统科学方法概论》第四章“控制方法”
    20世纪以来控制发展论有三个阶段,第一阶段是经典控制论阶段是20世纪4050年代。第2个是现代控制论阶段,20世纪6070年代第3个大系统理论阶段是20世纪70年代。控制论也有好多种分支情况。一工程控制论二生物控制论三社会控制论四人工智能。控制的定义所谓控制就是指在一定的环......
  • 控制方法
    控制论愿意是管理国家和”掌舵的艺术“。控制论的产生主要与两个因素有关,其一是生产自动化趋势的要求,其二是计算机技术的促进。现代控制论的建立和发展主要是同计算机科学、信息科学以及神经生理学的建立和发展联系起来的。到现在,控制论发展到现在已形成四大分支理论体系:工程控制......
  • Java流程控制04:if选择结构
    1.选择结构1.1if单选则结构1.1.1需要判断一个东西是否可行,然后再去执行,这样一个过程在程序中用if语句来表示1.1.2语法:if(布尔表达式){//如果布尔表达式为true将执行的语句}  1.2if......
  • Java流程控制03:顺序结构
    顺序结构:1.java的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行。2.顺序结构时最简单的算法结构。 3.语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个一次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构......