Java多线程之线程间通信

在这里插入代码片## 一、定义

线程与线程之间不是相互独立的个体,它们彼此之间需要相互通信和协作;

二、常用通信

2.1、CountDownLatch

定义:主线程等待其他线程,其他线程结束后,主线程才能执行(这里的主线程不一定是就是main主线程,强调的是你们先执行,执行完我再执行),强调的是一个线程等待多个线程先执行完,然后才执行;

  • 主线程阻塞等待,使用await方法,其他线程执行完,就会自动放行;
  • 其他线程执行完需要调用,countdown方法表示它已完成,也就是-1操作;

2.2、CyclicBarrier( 循环屏障)
定义: 等待的线程达到一定数目后再让它们继续执行(强调的是大家一起等待,人齐了才能执行),用户可以自定义 barrierAction参数, 则在阻塞线程数达到设定值屏障打开前,会调用barrierAction的run()方法完成用户自定义的操作;

  • 调用await方法阻塞线程,但是可以被中断(BrokenBarrierException, InterruptedException),一旦被中断(异常),也就是打破规则,其他等待的、还未执行的线程将不再遵守规则(也就是不再继续等待);

2.3、Semaphore( 信号量)
定义:控制多个线程访问特定的资源,主要用于限流(限制线程数);

  • 每一个需要访问的线程都必须先获取令牌,获取令牌后才能访问资源;
  • 获取令牌的方法acquire,该方法有多种重载,可尝试获取tryAcquire,可在指定的时间内获取等, - 获取锁后,务必记得释放release,否则其他的线程将无法获取令牌,继而无法访问资源了;
    具体使用方法可参考:https://blog.csdn.net/qq_42858175/article/details/106468541;

2.4、Wait/Notify
定义:基于Object类的wait和notify方法,进行线程间的通信;

  • wait和notify方法必须使用在Sychronized中;
  • wait方法最好放在while代码块中,防止虚假唤醒;

三、牛刀小试

1、CountDownLatch,CyclicBarrier,lock

package com.sun.springbootdemo.thread;

import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;

import java.text.DecimalFormat;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 

10个人抢红包 —— 资源类

* 1、红包必须抢完; * 2、10个人的红包累计金额不能超过总数,也就是10元 * 3、保证同时开始抢 * * @author Sundz * @date 2020/12/25 19:19 */
@Log4j2 public class QiangHongBao { public QiangHongBao(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } /** * @field 红包总金额 */ private volatile double total = 10; private AtomicInteger numberOfPeople = new AtomicInteger(0); /** * @field 保证主线程不会先执行,必须等10个人抢完所有的红包后才能执行 */ private CountDownLatch countDownLatch; /** * @field 数据格式化 */ private final DecimalFormat decimalFormat = new DecimalFormat("#.00"); /** * @field 保证10个人同时开始抢,也就是准备阶段,必须同步 */ private final CyclicBarrier barrier = new CyclicBarrier(10, () -> System.out.println("所有人都准备好了,可以开始抢红包了 -->> Beginning!!!")); private final Lock lock = new ReentrantLock(); public double handle() throws Exception { // 等待 必须等10个人都准备好了 才能开始抢红包 barrier.await(); lock.lock(); // 如果红包金额为0了 则说明红包已经被抢完了 double random = 0; // 保留2位小数即可 total = Double.parseDouble(decimalFormat.format(total)); long count = countDownLatch.getCount(); try { if (total == 0) { countDownLatch.countDown(); log.info(Thread.currentThread().getName() + " -->> 红包被抢完了,你手速太慢了!"); log.info("当前没抢红包的人数有:{}", count); return 0; } // 防止红包没有被抢完 强制最后一个人获取红包的剩余金额 if (numberOfPeople.get() == 9) { log.info("最后一个小伙伴( " + Thread.currentThread().getName() + ") -->> 抢到的红包金额为:{}元,红包剩余:{}元", total, 0); // 此时红包已被全部抢完了 total = 0; countDownLatch.countDown(); log.info("当前没抢红包的人数有:{}", count); return total; } random = getRandom(); /** * @field 改变红包金额 */ total -= random; // 统计抢红包的人数 +1 numberOfPeople.incrementAndGet(); log.info(Thread.currentThread().getName() + " -->> 抢到的红包金额为:{}元,红包剩余:{}元", random, total); /** * @field 每一个人抢完红后需要统计 防止主线程先执行 导致程序结束了 */ countDownLatch.countDown(); log.info("当前没抢红包的人数有:{}", count); } catch (Exception e) { log.error(Thread.currentThread().getName() + "获取锁时出现了异常!"); } finally { // 一定要释放锁 防止发生死锁 lock.unlock(); } return random; } /** * @field 获取红包随机数 */ public double getRandom() { Random random = new Random(System.nanoTime()); return Double.parseDouble(decimalFormat.format(random.nextDouble() * 2)); } @Test public void tets() throws Exception { CountDownLatch countDownLatch = new CountDownLatch(10); QiangHongBao q = new QiangHongBao(countDownLatch); ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(() -> { try { q.handle(); } catch (Exception e) { e.printStackTrace(); } }); } executorService.shutdown(); long count = countDownLatch.getCount(); countDownLatch.await(); log.info("红包抢完啦,感谢各位的参与!"); } public static void main(String[] args) throws Exception { CountDownLatch countDownLatch = new CountDownLatch(10); QiangHongBao q = new QiangHongBao(countDownLatch); ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(() -> { try { q.handle(); } catch (Exception e) { e.printStackTrace(); } }); } executorService.shutdown(); long count = countDownLatch.getCount(); countDownLatch.await(); log.info("红包抢完啦,感谢各位的参与!"); } }

2、Sychronized,Wait,NotifyAll

package com.sun.springbootdemo.thread;

import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;

/**
 * 

多个线程交替执行 == >> 线程间需要通信

* * @author Sundz * @date 2020/12/24 19:00 */
@Log4j2 public class AlternateExecute { @Test public void threadTest() { CountDownLatch countDownLatch = new CountDownLatch(5); CyclicBarrier cyclicBarrier = new CyclicBarrier(5); Semaphore semaphore = new Semaphore(2); Reset reset = new Reset(); new Thread(() -> { for (int i = 0; i < 10; i++) { reset.increase(); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { reset.increase(); } }, "C").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { reset.decrease(); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { reset.decrease(); } }, "D").start(); //TimeUnit.SECONDS.sleep(5); } class Reset { /** * @field 复位状态 */ int status = 0; public synchronized void increase() { try { // 阻塞 等待 防止多次累加 while (status != 0) { this.wait(); // 让出CPU的控制权 要用while防止虚假等待 } } catch (InterruptedException e) { log.error(e.getMessage(), e); } status++; log.info(Thread.currentThread().getName() + ", status:" + status); // 需要通知其他线程 可以进行减操作了 this.notifyAll(); } public synchronized void decrease() { try { // 当status为0时 需要等待 while (status == 0) { this.wait(); // 让出CPU的控制权 要用while防止虚假等待 } } catch (InterruptedException e) { log.error(e.getMessage(), e); } status--; log.info(Thread.currentThread().getName() + ", status:" + status); // 需要通知其他的线程 可以进行加操作了 this.notifyAll(); } } }

3、Lock,Condition

package com.sun.springbootdemo.thread;

import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;

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

/**
 * 

A,B,C -->> 3个字母轮流执行

* * @author Sundz * @date 2020/12/28 20:07 */
@Log4j2 public class RoundRobinExecution1 { /** * @field 输出字母数组 */ private static final String[] alphabetArr = { "A", "B", "C"}; private static final CountDownLatch latch = new CountDownLatch(9); /** * @field 多线程轮流输出 A, B, C, D */ static class Letter { /** * @field 可重入锁 */ private final Lock lock = new ReentrantLock(); /** * @field A监控器 对应输出A的线程 */ private final Condition conditionA = lock.newCondition(); /** * @field B监控器 对应输出B的线程 */ private final Condition conditionB = lock.newCondition(); /** * @field C监控器 对应输出C的线程 */ private final Condition conditionC = lock.newCondition(); /** * @field 用于标记线程 */ private int count = 1; /** * 输出字母A * * @param letter 要输出的字母 * @return {@link String} 返回值 */ public void printA(String letter) { lock.lock(); try { // 只要不是字母A则等待 while (count != 1) { conditionA.await(); // 等待 交出CPU控制权 } // 字母A 则输出,同时需要唤醒线程B(也就是输出B的线程) log.info(Thread.currentThread().getName() + "-->输出的字母为:{}", letter); count = 2; // 重置状态 conditionB.signal(); // 唤醒B线程 // A线程已完成 latch.countDown(); } catch (Exception e) { log.error(e.getMessage(), e); } finally { lock.unlock(); // 必须在finally中释放锁 否则会引起死锁 } } /** * 输出字母B * * @param letter 要输出的字母 * @return {@link String} 返回值 */ public void printB(String letter) { lock.lock(); try { // 只要不是字母B则等待 while (count != 2) { conditionB.await(); // 等待 交出CPU控制权 } // 字母B 则输出,同时需要唤醒线程C(也就是输出C的线程) log.info(Thread.currentThread().getName() + "-->输出的字母为:{}", letter); count = 3; // 重置状态 conditionC.signal(); // 唤醒C线程 // B线程已完成 latch.countDown(); } catch (Exception e) { log.error(e.getMessage(), e); } finally { lock.unlock(); // 必须在finally中释放锁 否则会引起死锁 } } /** * 输出字母B * * @param letter 要输出的字母 * @return {@link String} 返回值 */ public void printC(String letter) { lock.lock(); try { // 只要不是字母C则等待 while (count != 3) { conditionC.await(); // 等待 交出CPU控制权 } // 字母C 则输出,同时需要唤醒线程D(也就是输出D的线程) log.info(Thread.currentThread().getName() + "-->输出的字母为:{}", letter); count = 1; // 重置状态 conditionA.signal(); // 唤醒C线程 // C线程已完成 latch.countDown(); } catch (Exception e) { log.error(e.getMessage(), e); } finally { lock.unlock(); // 必须在finally中释放锁 否则会引起死锁 } } @Test public void threadTest() throws Exception { Letter letter = new Letter(); new Thread(() -> { for (int i = 0; i < 3; i++) { letter.printA(alphabetArr[0]); } }, "A").start(); new Thread(() -> { for (int i = 0; i < 3; i++) { letter.printB(alphabetArr[1]); } }, "B").start(); new Thread(() -> { for (int i = 0; i < 3; i++) { letter.printC(alphabetArr[2]); } }, "C").start(); //防止先主线程执行完 latch.await(); //TimeUnit.SECONDS.sleep(3); } } }

四、结束语

赠人玫瑰,手有余香,请留下你的赞或者宝贵意见吧!

你可能感兴趣的:(Java基础/高级,java)