IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> [Java基础] Java多线程之生活简述 -> 正文阅读

[Java知识库][Java基础] Java多线程之生活简述

前言

1024程序员节. 今天写点干货.

在Java的多线程内, 有非常多的组成和变量. 初学时, 经常拿捏不住. 下面我们带入生活的场景, 简单的描述下Java中的各种使用场景.


多线程的基本组成 Thread (多人相同工作)

在日常的生活中. 我们经常需要做一件事情. 我们认为时一个Task. 那么一个Task有时可以分成多个相同的Task, 以及多个不同的Task.

比如: 饺子店出售饺子

  • 场景1-1: 使用的是速冻饺子. 店家只需要负责下饺子就可以了. 那么当客人比较多的时候. 一口锅自然是不够的, 我们需要准备5口锅进行下饺子.
    那么, 你可以认为是有5个工人同时进行下饺子这个操作. 图示:

此时, 我们可以写出这样的伪代码.

// 下饺子
Task cookDumpling.
// 准备5个工人进行下饺子
Thread worker1 = new Thread(cookDumpling);
Thread worker2 = new Thread(cookDumpling);
Thread worker3 = new Thread(cookDumpling);
Thread worker4 = new Thread(cookDumpling);
Thread worker5 = new Thread(cookDumpling);
// 发号司令 5个人开始干活
worker1.start();
worker2.start();
worker3.start();
worker4.start();
worker5.start();
  • 场景1-2: 当然, 我们并不希望. 这5个工人直接下完一次饺子. 就可以停止工作. (老资本家了.) 所以, 我们希望他们循环工作. 并且没有工作的时候, 也不要停止工作, 而是进行等待. 所以, 我们可以将工人的task和run方法分离. 可以写成这样的伪代码.
// 工作池. 所有的需求不断向工作池中放即可.
List<Task> queueTaskList = new Queue();

// 任务类
class Task{
    // 任务的Id
    int id;
    // 订单是啥 - 比如这个客人订了2份鲜肉水饺. 2份韭菜水饺.
    Order order;
}
// 工作类
class Worker{
    public void work(){
        // 查看当前时间是否是工作时间. 如果是工作时间. 就给我不停的干活.    
        while(currentTime>startTime & currentTime<endTime){
            if(queueTaskList.isNotEmpty()){
                // 弹出最前的需要执行的任务                
                var task = queueTaskList.pop();
                // 执行这个任务
                task.perform();
            }else{
                // 否则过1分钟过后. 再来查看工作池中是否有工作需要干.            
            }
        }
    }
}
  • 场景1-3: 有时, 我们服务的时候, 可以选择专门的人员. 比如看病时, 我们希望选择专门的医生. 修水管时, 选择专门的水管工.(或者叫技师,嘿嘿.)

这样的使用, 也不难. 我们可以重写task的分发和抽取方法即可.

class Task{
    // 需要指定的workerid 指定你需要执行任务的技师
    String workerId;
}
// 工作类
class Worker{
    public void work(){
        while(currentTime>startTime & currentTime<endTime){
            if(queueTaskList.isNotEmpty()){
                // 写的简单点. 我们可以判断当前头部任务是否是需要我来做的.
                var task = queueTaskList.head();
                if(task.workerId.equals(this.id)){
                    // 指定的技师是当前工人. 那么就由我来拯救你吧.
                    // 从任务池中接下任务, 然后执行任务.
                    var task = queueTaskList.pop();
                    task.perform();
                }else{
                    // 休息10秒中. 等其他技师接下任务.
                }

            }else{
                // 否则过1分钟过后. 再来查看工作池中是否有工作需要干.            
            }
        }
    }
}

多人不同工作 - 无相互关联

上面的讲述. 主要讲解. 我们如何让多人协作完成相同的一件事情. 当然, 在生活中, 我们经常遇到的情况却不是这样. 我们经常遇到的事情是, 需要同时执行2件不相同的事情. 还是以包饺子为例, 我们都需要买菜. 1. 需要买10斤肉. 2. 需要买10斤韭菜. 那么这2件事情, 同时也是可以让2个人来做的. 当然, 你可以可以让2个人来买这10斤肉. 全凭你的喜好. 我们的伪代码应当这样写:

// 买韭菜
Task buyChives
// 买肉
Task buyMeat.
// 准备2个工人分别干这个活
Thread worker1 = new Thread(buyMeat);
Thread worker2 = new Thread(buyChives);
// 发号司令 2个人开始干活
worker1.start();
worker2.start();

当然, 你可以可以让2个人来买这10斤肉. 全凭你的喜好. 此时, 伪代码应该可以这样写.

// 买韭菜
Task buyChives
// 买肉
Task buyMeat.
// 准备3个工人分别干这个活
Thread worker1_1 = new Thread(buyMeat);
Thread worker1_2 = new Thread(buyMeat);
Thread worker2 = new Thread(buyChives);
// 发号司令 3个人开始干活
worker1_1.start();
worker1_2.start();
worker2.start();

多人不同的工作 - 有相互关联

在生活中. 我们经常遇到的事情, 有时并不是单纯的买菜. 而是比较复杂的, 有相互依赖关系的2件, 或者多件事情. 那么我们又应该如何做呢?

还以包饺子为例. 我们需要人1. 买菜, 2. 包饺子 3. 下饺子. 这我们选择3波人同时开始做. 但是… 这3件事情是有依赖关系的. 为了追求方便性和速度. 我们约定, 2个人买菜. 2个人包饺子. 2个人下饺子. 并且包饺子人需要等买菜结束后才能执行. 下饺子必须等包饺子结束后才能执行.
那么, 这个流水线操作, 就形成了. 我们可以设置如下的开关.

// 买菜任务
Task bugVegetable
// 包饺子任务
Task makeDumplings
// 下饺子任务
Task cookDumplings

// 准备6个工人分别干这个活
Thread worker1_1 = new Thread(bugVegetable);
Thread worker1_2 = new Thread(bugVegetable);
Thread worker2_1 = new Thread(makeDumplings);
Thread worker2_2 = new Thread(makeDumplings);
Thread worker3_1 = new Thread(cookDumplings);
Thread worker3_2 = new Thread(cookDumplings);

// 买菜先行
worker1_1.start();
worker1_2.start();

// 等待买菜结束
worker1_1.join();
worker1_2.join();

// 包饺子第二
worker2_1.start();
worker2_2.start();

// 等待包饺子结束
worker2_1.join();
worker2_2.join();

// 下饺子第三
worker3_1.start();
worker3_2.start();

此处的等待当然在Java内, 可以使用Thread的Join方法或者CountDownLatch等工具类实现. 此处略.


Java中的锁实现其实有非常多的种类. 但是日常生活中, 我们锁的抽象都是一样的. 所以此处就不再举现实的例子进行映射了.

  • Java内的锁: Synchronized
  • Java锁: ReentanckLock / ReentrackReadWriteLock
  • 锁工具类: CountDownLatch / CycleBarrier / Semaphore
  • 限流器: Semaphore
  • 交流器: Exchanger
  • 可视化同步关键字: volatile

锁的实现, 在Java内也是可以通过直接编译参数, 或者CAS进行实现的.

由于此处篇幅有限. 此处就不一一赘述了.


通信

Java内的通信其实可以分为线程间的信号通信和直接通信. 信号的通信主要是Thread的wait和notify方法. 当然还有ReentackLock中的condition方法. 直接通信的话, 可以通过方法写入和消息池2种.

  • 信号通信: wait/notify/notifyAll | condition
  • 消息通信: sendMessage(People people, Message message) | 消息池(消息通道)

异步操作

我们日常生活中. 做事经常不是同步的操作. 什么是同步操作, 什么是异步操作? 非常简单. 举个例子:

  • 老板让小王买菜. 小王买菜回来之前, 老板都一直等待. 此处是同步操作.(怕不是老板是老板娘, 买菜的是老板. HAHA)

  • 老板让小王买菜. 小王买菜回来之前, 老板会做一些其他事情, 比如打游戏等待. 此处是异步操作.

### 同步伪代码
// 买菜任务
Task bugVegetable
Thread workerWang = new Thread(bugVegetable);
workerWang.start();
// 老板同步等待
workerWang.join();
### 异步伪代码
// 买菜任务
Task bugVegetable
Thread workerWang = new Thread(bugVegetable);
workerWang.start();
// 老板干其他事情 - 比如打游戏
game();

// 老板等待 - 老板等不及了. 要记账. 咋还没回来呢. 开始同步等待. 获取买菜结果.
workerWang.join();
### 异步伪代码 Future
// 买菜任务
Task bugVegetable
Future<Callable> workerWang = new Thread(bugVegetable);
workerWang.start();
// 老板干其他事情 - 比如打游戏
game();

// 老板等待 - 老板等不及了. 要记账. 咋还没回来呢. 开始同步等待. 获取买菜结果.
workerWang.get();

此处后2处的伪代码其实并没有什么区别的地方. 主要一个是使用了Java的Thread的join方法, 一个使用了Future的get方法. 其实本质并无区别.


原子操作

日常生活中的最小节点. 比如你做作业. 就不是最小节点. 做数学作业, 也不是. 计算一道数学题也不是. 但是"1+1=2"的计算工作可以认为是一个生活中的原子操作的.


安全的同步容器

比如我们有时候. 同时有2个人进行买菜, 他们都从账房进行取钱.
此时账房金额为1000, A取100, 将要把900写入.
此时B也取了100, 也要把900写入.此时B写入900.
此时A也写入900.
那么此时账房金额为900. 那么2个人都取了100. 那么谁亏了呢… HEI~ HEI~ 100快哪去了.

答案: 其实账房明显亏了. 被取了200. 写成取100. 账房实际还有800. 但是写900. 有100差值.

整体的流程是这样的:

  • 账房1000.
  • A取100. 计算出还剩900.
  • B取100. 计算出还剩900.
  • B写入900.
  • A写入900
  • 账房还剩800. 但是记账为900. 100错误.
那么怎么处理?
  • 每次取的时候. 都基于本次-100. 但是此方法估计会减少为复数吧.(uptdate total=total-100 where total >=100;)
  • 强制锁. 可以使用volatile, 也可使用sychronized的思路.
### synchronized的思路 (整个更新全部加锁. 悲观锁.)
> * 账房1000.
> * 锁定账房. A取100. 计算出还剩900.
> * B取100. 失败. 因为已经被A锁定了. 无法获取.
> * A写入900. 释放锁.
> * B获取锁. 锁定账房. B取100, 计算出还剩800.
> * B写入800. 释放锁.
> * 账房结束还剩800. 记账800. 此时就是正确的.
### volatile思路 (只在写入时加乐观锁)
> * 账房1000.
> * 锁定账房. A取100. 计算出还剩900.
> * B取100. 计算出还剩900.
> * A写入900. 释放锁.
> * B获取锁. 发现账房已经被A动过. 重新计算. 计算出等于800.
> * B写入800. 释放锁.
> * 账房结束还剩800. 记账800. 此时就是正确的.

工作池 & 工作池框架

生活中. 经常有一套方案. 比如指派2个人进行买菜, 2人进行包饺子. 那么 2人进行销售, 2人进行记账. 这样的方案, 我们经常认为时线程池.
在Java内我们非常多的线程池种类. 比如固定人数的线程池, 可变人数的线程池, 定时线程池. 当然为了让线程池这样的方案更容易使用, 我们还又包装了一层的Executors. 这不是本篇的主要议题. 那么就简单的简要的略过了.


总结

本文主体介绍了Java中的多线程与现实的使用场景. 明显人可以看出, 其中的Java相关的影子.

  • 工人: Thread
  • 线程同步: Join
  • 线程异步: Future Callable
  • 其他同步工具: CountDownLatch / CycleBarrier / Semaphere
  • 锁: Synchronized / ReentackLock / 非锁工具 volatile
  • 原子操作: AtomicInteger
  • 消息通信: wait & notify / condition /消息Queue
  • 线程池: ThreadPool / Executor
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-25 12:24:25  更:2021-10-25 12:25:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:05:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码