协程和线程的区别、协程原理与优缺点分析、在Java中使用协程

news2024/12/23 20:14:41

文章目录

    • 什么是协程
    • 协程的优点与缺点
    • 协程实现原理.
    • 协程与线程在不同编程语言的实现
    • 在Java中使用协程
      • Kilim介绍
      • Kilim整合Java,使用举例
    • 小总结

什么是协程

相对于协程,你可能对进程和线程更为熟悉。进程一般代表一个应用服务,在一个应用服务中可以创建多个线程,而协程是一个轻量级线程,我们知道线程是CPU调度的基本单位,也就是说它是由CPU进行调度的,而协程的调度由用户空间中其专属的调度器控制的,这是一个很大的不同.我们可以在一个主线程里面轻松创建多个协程。

上面提到的协程专属的调度器其实就是程序员自己写的一个东西,不属于操作系统的,只是用来调度协程的,但是协程的执行其实还是由线程执行的,协程可以认为是运行在线程上的代码块.
这里举个例子假如人就是一个线程,那么协程就是具体的做某一件事情,一个人可以并发的做好几件事情,比如说可以同时煮饭,热水,听歌等等,这些事情可能我们就只需要偶尔操作一下,不需要完全占用我们的时间,就类似于我们的IO密集型任务,比较适合协程来做. 而其他的任务,比如说跑步,需要一直占用我们的时间,类似于CPU密集型任务,就不适合协程来做.

在对协程进行调度时,可以通过暂停或者阻塞的方式将协程的执行挂起,而其它协程可以继续执行。这里的挂起只是在程序中(用户态)的挂起,同时将代码执行权转让给其它协程使用,待获取执行权的协程执行完成之后,将从挂起点唤醒挂起的协程 协程的挂起和唤醒是通过一个调度器来完成的。

协程的优点与缺点

优点:

  1. 协程是轻量级线程,全部都在用户态,因此系统消耗资源非常低,非常高效.不像线程一样是内核线程,由cpu调度,造成上下文切换,浪费资源.
  2. 线程实现数据共享的方式是共享内存,而协程是通信,这就避免了线程安全的问题,避免了锁竞争.

缺点:

  1. 系统是察觉不到协程的存在的,所以只有一个处理器内核会被分配给该进程 ,也就不能发挥多核 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,使用举例

这里我们实现一个简单的生产者和消费者的案例.

  1. 引入maven
        <dependency>
            <groupId>org.db4j</groupId>
            <artifactId>kilim</artifactId>
            <version>2.0.1-jdk7</version>
        </dependency>
  1. 代码实现
    在这里插入图片描述
    消费者:
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,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

源码看CAF的线程调度框架

序 本篇文章带着大家来看下CAF&#xff08;C Actor Framwwork&#xff09;的调度框架&#xff0c;也是算现阶段C比较成熟的调度框架&#xff0c;大家如果自己完成一个比较大型的项目&#xff0c;任务调度也可以参照CAF。 鉴于篇幅较长&#xff0c;大家如果学习使用如何使用CAF…

修改jupyter notebook默认路径

修改jupyter notebook默认路径jupyter notebook默认打开C:\Users\你的用户名&#xff0c;用户名是你的电脑用户名&#xff0c;upload文件又会在C盘生成一堆文件&#xff0c;很乱&#xff0c;用notebook打开文件还要跳转到目录&#xff0c;很麻烦&#xff0c;那有没有办法呢&…

【PYTHON】如何配置集成开发环境Geany

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

【笔记】大话设计模式17-20

【笔记】大话设计模式17-20 文章目录【笔记】大话设计模式17-2017 适配器模式17.1 Example17.2 定义17.3 Show me the code17.4 总结18 备忘录模式18.1 Example18.2 定义18.3 Show me the code18.4 总结19 组合模式19.1 Example19.2 定义19.3 Show me the code19.4 总结20 迭代…

基于python的人工智能数据处理常用算法

文章目录二分法求解最小二乘法曲线拟合最小二乘法的来历最小二乘法与曲线拟合多项式曲线拟合SciPy内置最小二乘法应用泰勒级数背景引入泰勒公式泰勒级数展开与多项式近似二分法求解 机器学习过程中往往会用到很多变量&#xff0c;而这些变量之间的复杂关系一般用非线性方程来&…

VS系列知识-VS Code的安装+Vue环境的搭建+Vue指令

一、VS Code下载地址 Visual Studio Code - Code Editing. Redefined 二、VS Code初始化设置 1.安装插件 在安装好的VSCode软件的扩展菜单中查找安装如下4个插件 2、创建项目 vscode本身没有新建项目的选项&#xff0c;所以要先创建一个空的文件夹&#xff0c;如project_xx…

【用三大件写出的开门烟花特效】

又到了一年一度的春节时期啦&#xff01;昨天呢是北方的小年&#xff0c;今天是南方的小年&#xff0c;看到大家可以愉快的放烟花&#xff0c;过大年很是羡慕呀&#xff01;辞旧岁&#xff0c;贺新春&#xff0c;今年我呀要放烟花&#xff0c;过春节&#xff01;&#x1f9e8;。…

云原生|kubernetes|2022年底cks真题解析(1-10)

前言&#xff1a; cka和cks认证真的比较恶心&#xff0c;他们的那个PSI Bridge Secure Browser真的非常卡。 吐槽完毕&#xff0c;不废话&#xff0c;直接上真题解析。 CKS总共是16道题&#xff0c;题目顺序是打乱的&#xff0c;由于认证系统非常卡&#xff0c;因此&#xf…

通讯录最终版——动态存储+文件处理

最终版通讯录即从上一个版本修改过来先看总体代码&#xff0c;我们再看看差异ps&#xff1a;里面涉及到很多函数的使用&#xff0c;后续我会出专栏来书写这些函数的使用和实例&#xff0c;与常见错误大家可以通过https://cplusplus.com查找test.c#define _CRT_SECURE_NO_WARNIN…

Spring入门-IOC/DI入门与使用文件配置管理(1)

文章目录Spring入门1&#xff0c;Spring介绍1.1 为什么要学?1.2 学什么?1.3 怎么学?2&#xff0c;Spring相关概念2.1 初识Spring2.1.1 Spring家族2.1.2 了解Spring发展史2.2 Spring系统架构2.2.1 系统架构图2.2.2 课程学习路线2.3 Spring核心概念2.3.1 目前项目中的问题2.3.…

已解决:无法解析 jdk.tools:jdk.tools:1.6

文章目录问题描述解决方案问题描述 HBase API客户端操作时&#xff0c;报错&#xff1a;无法解析 jdk.tools:jdk.tools:1.6 这种问题司空见惯了&#xff0c;无非是依赖没下载&#xff0c;版本问题&#xff0c;依赖没加载成功&#xff0c;文件索引没更新成功&#xff0c;IDEA文…

大数据-Hadoop的介绍、配置和集群的使用

HDFS分布式文件系统 分布式&#xff1a;将多台服务器集中在一起&#xff0c;每台服务器都实现总体中的不同业务&#xff0c;做不同的事情 单机模式 厨房里只有一个人&#xff0c;这个人既要买菜&#xff0c;又要切菜&#xff0c;还要炒菜&#xff0c;效率低。 分布式模式 厨房…

leetcode2293:极大极小游戏(1.15每日一题)

题目表述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;终止 算法过程。否则&#xff0c;创建 一个新的整数数组 newNums &#xff0c;新数…

深浅copy

go 在go语言中值类型赋值都是深拷贝&#xff0c;引用类型一般都是浅拷贝其本质就是&#xff0c;深拷贝会拷贝数据&#xff0c;而浅拷贝只会拷贝内存的地址&#xff0c;所有就会出现&#xff0c;像slice那样修改底层数组的值&#xff0c;slice的值也跟着改动。 深拷贝 修改a的…

[iHooya]1月15日寒假班作业解析

过滤多余的空格 一个句子中也许有多个连续空格&#xff0c;过滤掉多余的空格&#xff0c;只留下一个空格。 输入&#xff1a;一行&#xff0c;一个字符串&#xff08;长度不超过200&#xff09;&#xff0c;句子的头和尾都没有空格。 输出&#xff1a;过滤之后的句子。 样例输…

全球各国机场名称、坐标经纬度、高程数据(更新至2022年)

数据来源&#xff1a;自主整理 时间跨度&#xff1a;更新至2022 区域范围&#xff1a;全球各国 指标说明&#xff1a; 全球机场坐标数据&#xff0c;包含CSV格式、shpfile格式、kml格式属性字段包括机场类型、经纬度&#xff0c;高程&#xff0c;所在国家省市区域&#xff…

5.12回溯法--连续邮资问题--子集树

回溯法的题目太多了&#xff0c;不想写这个代码了&#xff0c;于是我就开始水一篇文章&#xff0c;就单纯的分析一下这个问题保持整本书完整的队形 问题描述 如何用有限的邮票数&#xff0c;贴出更多面额的需求&#xff1f; 举例 n5&#xff0c;m4 设计1&#xff1a;X1{1, …

20多年老码农的IT学习之路

20年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0c;年薪税前150万多&#xff0e;最近公司业绩不好&#xff0c;有感觉工作不保&#xff0c;所以又捡起了编程&#xff0c;开始学习Golang&#xff0c;Angular等。我不是985&#xff0c;211也不是海归&#xff0c;我…

基于ssm+mysql+jsp实现在线花店

基于ssmmysqljsp实现在线花店一、系统介绍1、系统主要功能&#xff1a;2、环境配置二、功能展示1.主页(客户)2.登陆&#xff08;客户&#xff09;3.我的购物车(客户)4.我的订单&#xff08;客户&#xff09;5.主页&#xff08;管理员&#xff09;6.订单管理&#xff08;管理员&…

什么是链路追踪?分布式系统如何实现链路追踪?

在分布式系统&#xff0c;尤其是微服务系统中&#xff0c;一次外部请求往往需要内部多个模块&#xff0c;多个中间件&#xff0c;多台机器的相互调用才能完成。在这一系列的调用中&#xff0c;可能有些是串行的&#xff0c;而有些是并行的。在这种情况下&#xff0c;我们如何才…