协程和线程的区别、协程原理与优缺点分析、在Java中使用协程
文章目录
- 什么是协程
- 协程的优点与缺点
- 协程实现原理.
- 协程与线程在不同编程语言的实现
- 在Java中使用协程
- Kilim介绍
- Kilim整合Java,使用举例
- 小总结
什么是协程
相对于协程,你可能对进程和线程更为熟悉。进程一般代表一个应用服务,在一个应用服务中可以创建多个线程,而协程是一个轻量级线程,我们知道线程是CPU调度的基本单位,也就是说它是由CPU进行调度的,而协程的调度由用户空间中其专属的调度器控制的,这是一个很大的不同.我们可以在一个主线程里面轻松创建多个协程。
上面提到的协程专属的调度器其实就是程序员自己写的一个东西,不属于操作系统的,只是用来调度协程的,但是协程的执行其实还是由线程执行的,协程可以认为是运行在线程上的代码块.
这里举个例子假如人就是一个线程,那么协程就是具体的做某一件事情,一个人可以并发的做好几件事情,比如说可以同时煮饭,热水,听歌等等,这些事情可能我们就只需要偶尔操作一下,不需要完全占用我们的时间,就类似于我们的IO密集型任务,比较适合协程来做. 而其他的任务,比如说跑步,需要一直占用我们的时间,类似于CPU密集型任务,就不适合协程来做.
在对协程进行调度时,可以通过暂停或者阻塞的方式将协程的执行挂起,而其它协程可以继续执行。这里的挂起只是在程序中(用户态)的挂起,同时将代码执行权转让给其它协程使用,待获取执行权的协程执行完成之后,将从挂起点唤醒挂起的协程 协程的挂起和唤醒是通过一个调度器来完成的。
协程的优点与缺点
优点:
- 协程是轻量级线程,全部都在用户态,因此系统消耗资源非常低,非常高效.不像线程一样是内核线程,由cpu调度,造成上下文切换,浪费资源.
- 线程实现数据共享的方式是共享内存,而协程是通信,这就避免了线程安全的问题,避免了锁竞争.
缺点:
- 系统是察觉不到协程的存在的,所以只有一个处理器内核会被分配给该进程 ,也就不能发挥多核 CPU 的优势,所以协程适用于I/O 阻塞型场景.不适用于cpu密集型.
协程实现原理.
这里还是对比着线程来说.
线程是被内核所调度,线程被调度切换到另一个线程上下文的时候,需要保存一个用户线程的状态到内存(切出),恢复另一个线程状态到寄存器(切入),然后更新调度器的数据结构,这几步操作涉及到用户态到内核态的切换,开销比较多.
那协程是怎样被调度?被执行的呢?
它其实是完全在用户空间实现了自己的一套调度器,上下文存储,以及任务载体等等这些,相当于我直接在用户空间就能完成程序的调度与切换,完全没有内核切换的开销.
协程的执行其实还是依靠线程来执行的.所以,同一时间, 在多核处理器的环境下, 多个线程是可以并行的,但是运行的协程的函数却只能有一个,其他的协程的函数都被suspend, 即协程是并发的.
协程与线程在不同编程语言的实现
Go语言最近非常火,国内很多互联网公司开始使用或转型 Go 语言,其中一个很重要的原因就是 Go 语言优越的性能表现,而这个优势与 Go 实现的轻量级线程 Goroutines(协程 Coroutine)不无关系.
Golang 在语言层面实现了对协程的支持,Goroutine 是协程在 Go 语言中的实现, 在 Go 语言中每一个并发的执行单元叫作一个 Goroutine ,Go 程序可以轻松创建成百上千个协程并发执行。
JDK 1.8 Thread.java 中 Thread#start 方法的实现,实际上是通过 Native 调用 start0 方法实现的;在 Linux 下, JVM Thread 的实现是基于 pthread_create 实现的,而 pthread_create 实际上是调用了 clone() 完成系统调用创建线程的。也就是创建的是内核线程.目前 Java 原生语言暂时还不支持协程.
在Java中使用协程
Kilim介绍
虽然目前 Java 原生语言暂时还不支持协程。不过也不用泄气,我们可以通过协程框架在 Java 中使用协程。
目前 Kilim 协程框架在 Java 中应用得比较多,通过这个框架,开发人员就可以低成本地在 Java 中使用协程了。
Kilim 框架包含了四个核心组件,分别为:任务载体(Task)、任务上下文(Fiber)、任务调度器(Scheduler)以及通信载体(Mailbox)。
任务载体Task:
Task对象主要用来执行业务逻辑,我们可以把这个比作多线程的Thread,与Thread类似,Task中也有一个run方法,不过在Task中方法名为execute,我们可以将协程里面要做的业务逻辑操作写在execute方法中.
与 Thread 实现的线程一样,Task 实现的协程也有状态,包括:Ready、Running、Pausing、Paused 以及 Done 总共五种。Task 对象被创建后,处于 Ready 状态,在调用 execute() 方法后,协程处于 Running 状态,在运行期间,协程可以被暂停,暂停中的状态为 Pausing,暂停后的状态为 Paused,暂停后的协程可以被再次唤醒。协程正常结束后的状态为 Done。
任务上下文Fiber:
Fiber 对象与 Java 的线程栈类似,主要用来维护 Task 的执行堆栈,对于实现协程切换很关键.
任务调度器Scheduler:
Scheduler是实现协程的核心调度器,Scheduler负责分派Task给指定的工作者线程执行,工作者线程默认初始化个数为机器的CPU个数.
邮箱Mailbox:
Mailbox对象类似一个邮箱,协程之间可以依靠邮箱来进行通信和数据共享,这里和线程是很大的区别,因为线程是共享内存来实现
Kilim整合Java,使用举例
这里我们实现一个简单的生产者和消费者的案例.
- 引入maven
<dependency><groupId>org.db4j</groupId><artifactId>kilim</artifactId><version>2.0.1-jdk7</version></dependency>
- 代码实现
消费者:
public class Consumer extends Task<Object> {Mailbox<Integer> mb = null;public Consumer(Mailbox<Integer> mb) {this.mb = mb;}/*** 执行*/public void execute() throws Pausable {Integer c = null;for (int i = 0; i < 10; i++) {c = mb.get();//获取消息,阻塞协程if (c == null) {System.out.println("计数");}else {System.out.println(Thread.currentThread().getName() + "消费者消费,目前总共有" + mb.size() + "消费了:" + c);c = null;}}}}
生产者:
public class Producer extends Task<Object> {Integer count = null;Mailbox<Integer> mb = null;public Producer(Integer count, Mailbox<Integer> mb) {this.count = count;this.mb = mb;}public void execute() throws Pausable {count = count*10;for (int i = 0; i < 10; i++) {mb.put(count);//当空间不足时,阻塞协程线程System.out.println(Thread.currentThread().getName() + "生产者生产,目前总共有" + mb.size() + "生产了:" + count);count++;}}}
主类:
public class Coroutine {static Map<Integer, Mailbox<Integer>> mailMap = new HashMap<Integer, Mailbox<Integer>>();public static void main(String[] args) {if (kilim.tools.Kilim.trampoline(false,args)) return;Properties propes = new Properties();propes.setProperty("kilim.Scheduler.numThreads", "4");System.setProperties(propes);long startTime = System.currentTimeMillis();//创建一千个协程生产者发送消息for (int i = 0; i < 1000; i++) {Mailbox<Integer> mb = new Mailbox<Integer>(1, 10);new Producer(i, mb).start();mailMap.put(i, mb);}//创建一千个协程消费者消费消息for (int i = 0; i < 1000; i++) {new Consumer(mailMap.get(i)).start();}Task.idledown();long endTime = System.currentTimeMillis();System.out.println( Thread.currentThread().getName() + "总计花费时长:" + (endTime- startTime));}}
运行结果:
可以看到运行时间还是很短的,这里我们用Java线程实现类似的功能,看看性能差异.
代码如下:
public class ThreadTest {private static Integer count = 0;private static final Integer FULL = 10;private static String LOCK = "lock";private static CountDownLatch countDownLatch = new CountDownLatch(2000);public static void main(String[] args) {ThreadTest test1 = new ThreadTest();long start = System.currentTimeMillis();List<Thread> list = new ArrayList<Thread>();//创建1000个生产者,每个生产者每次生产10个产品for (int i = 0; i < 1000; i++) {Thread thread = new Thread(test1.new Producer());thread.start();list.add(thread);}//创建1000个消费者,每个消费者每次生产消费个产品for (int i = 0; i < 1000; i++) {Thread thread = new Thread(test1.new Consumer());thread.start();list.add(thread);}try {//等待所有的线程运行完毕countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start));}class Producer implements Runnable {public void run() {for (int i = 0; i < 10; i++) {//因为是共享内存,所以这里需要加锁,确保线程安全synchronized (LOCK) {while (count == FULL) {try {LOCK.wait();} catch (Exception e) {e.printStackTrace();}}count++;System.out.println(Thread.currentThread().getName() + "生产者生产,目前总共有" + count);LOCK.notifyAll();}}countDownLatch.countDown();}}class Consumer implements Runnable {public void run() {for (int i = 0; i < 10; i++) {synchronized (LOCK) {while (count == 0) {try {LOCK.wait();} catch (Exception e) {}}count--;System.out.println(Thread.currentThread().getName() + "消费者消费,目前总共有" + count);LOCK.notifyAll();}}countDownLatch.countDown();}}
}
运行结果:
可以看到协程花费400,线程花费1300,差距还是蛮大的.
小总结
协程和线程密切相关,协程可以认为是运行在线程上的代码块,协程提供的挂起操作会使协程暂停执行,而不会导致线程阻塞。
协程又是一种轻量级资源,即使创建了上千个协程,对于系统来说也不是很大的负担,但如果在程序中创建上千个线程,那系统可真就压力山大了。
可以说,协程的设计方式极大地提高了线程的使用率。通过今天的学习,当其他人侃侃而谈 Go 语言在网络编程中的优势时,相信你不会一头雾水。学习 Java 的我们也不要觉得,协程离我们很遥远了。
协程是一种设计思想,不仅仅局限于某一门语言,况且 Java 已经可以借助协程框架实现协程了。但话说回来,协程还是在 Go 语言中的应用较为成熟,在 Java 中的协程目前还不是很稳定,重点是缺乏大型项目的验证,可以说 Java 的协程设计还有很长的路要走。
今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
协程和线程的区别、协程原理与优缺点分析、在Java中使用协程相关推荐
- Go 面试官:什么是协程,协程和线程的区别和联系?
大家好,我是煎鱼. 最近金三银四,是面试的季节.在我的 Go 读者交流群里出现了许多小伙伴在讨论自己面试过程中所遇到的一些 Go 面试题. 今天的男主角,是工程师的必修技能,那就是 "什么是 ...
- 什么是协程,协程和线程的区别和联系?
1 进程 进程是什么 进程是操作系统对一个正在运行的程序的一种抽象,进程是资源分配的最小单位. 进程在操作系统中的抽象表现 为什么有进程 为什么会有 "进程" 呢?说白了还是为了合 ...
- NO.7 Monitor(管程)是什么意思?Java中Monitor(管程)的介绍
目录 一.Monitor的概念 二.Monitor 基本元素 三.Java 语言对 monitor 的支持 临界区的圈定 monitor object synchronized 关键字 四.管程:并发 ...
- java管程 实现,Java中的管程模型
Java中的管程模型 操作系统使用信号量解决并发问题,Java选择使用管程(Monitor)解决并发问题.信号量和管程是等价的,可以使用信号量实现管程,也可以使用管程实现信号量. 管程就是指管理共享变 ...
- 2.3.7 操作系统之管程和java中实现管程的机制
文章目录 0.思维导图 1.为什么引入管程? 2.管程的组成及基本特征 3.管程实现生产者消费者问题 4.java中类似于管程的机制 0.思维导图 1.为什么引入管程? 2.管程的组成及基本特征 3. ...
- [转帖]LCD与LED的区别之背光原理与优缺点对比介绍
LCD与LED的区别之背光原理与优缺点对比介绍 http://m.elecfans.com/article/620376.html 时下液晶面板与液晶电视技术已经达到炉火纯青的境界,并已经成为大屏幕平 ...
- Unity协程和线程的区别
先简要说下结论: 协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针(IP,instruction pointer),但与其它协同程序共享全局变 ...
- 【golang】协程和线程的区别
线程是系统调度的基本单位.go协程由go语言运行时的调度器进行调度,操作系统内核感知不到协程的存在. 在多核处理场景中,线程是并发与并行同时存在的,而go协程依托于线程,因此多核处理场景下,go协程也 ...
- Unity中协程与线程的区别
本文转载自:https://blog.csdn.net/qq_25122429/article/details/80481443 协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆 ...
最新文章
- 信号 频率_信号的时间域分辨率和频率域分辨率
- 对象 普通po转_谈谈C++对象的构造
- 期望+DP ZOJ 3929 Deque and Balls
- .NET中的UI自动化测试
- SAP Spartacus里的converter实例化逻辑
- 高通量数据中批次效应的鉴定和处理(六)- 直接校正表达矩阵
- CSS3蒙版/遮罩、倒影
- Linux学习总结(32)——Shell脚本高效编写技巧
- python类概念是什么_python中类的概念
- readonly(C# 参考)
- Linux下Tomcat性能优化--文件句柄数增大
- 微信小程序傻瓜制作_从15款工具中精选出4款,最靠谱的微信小程序制作软件!...
- 二阶系统的时间响应及动态性能(时域分析)
- tcl语言读取文件一行_TCL语言(九) 路径和文件
- 傅里叶变换、拉普拉斯变换、Z 变换的联系是什么?为什么要进行这些变换?
- mysql跳跃扫描_MySQL 8.0 之索引跳跃扫描(Index Skip Scan)
- audio接线图解_5.1家庭影院布线之音响如何接线(图文教程)
- Excel使用技巧—每30行取1行数据
- 区块链应用_资产证券化
- pytorch中的神经网络模块基础类——torch.nn.Module