ThreadX学习(2)——线程_threadx教程-程序员宅基地

技术标签: ThreadX  多线程  

学习参考:

ThreadX中的线程

在ThreadX中,一般没有进程的概念,统称为线程

在这里插入图片描述
关于调度器的实现细节,ThreadX可能是用汇编写的,没看懂。但大概是一个寻找最高优先级线程来运行的循环,通过_tx_thread_execute_ptr变量来表示下一个运行的线程。其他线程状态变化的API也会修改这个变量。

ThreadX没有对可以创建的线程数量或者可以使用的优先级组合进行限制。然而,为了优化性能和最小化目标规模,开发人员应该遵循以下准则:

  • 最小化应用程序系统中的线程数
  • 仔细选择优先级:防止低优先级线程饥饿。
  • 优先级尽量少:具有许多不同线程优先级的应用程序本质上比具有较少优先级的应用程序需要更多的系统开销。
  • 考虑抢占阈值
  • 使用互斥锁时考虑优先级继承
  • 考虑循环调度
  • 考虑时间片
  • 保证线程只完成特定的工作单元,而不是一系列不同的操作

为了理解线程优先级对上下文切换开销的影响,考虑一个线程名为thread_1、thread_2和thread_3的三线程环境。此外,假设所有线程都挂起并等待消息。当thread_1收到消息时,它会立即将消息转发给thread_2。然后,Thread_2将消息转发给thread_3。Thread_3简单地丢弃了该消息。在每个线程处理其消息后,它将再次挂起自己,等待另一条消息。执行这三个线程所需的处理根据它们的优先级有很大的不同。如果所有线程具有相同的优先级,则在每个线程的执行之间会发生单个上下文切换。当每个线程挂起一个空消息队列时,就会发生上下文切换。

但是,如果thread_2的优先级高于thread_1, thread_3的优先级高于thread_2,则上下文切换的数量会翻倍。这是因为当tx_queue_send服务检测到一个高优先级线程已经就绪时,它内部会发生另一个上下文切换。(现在不懂,之后来改)

如果需要为这些线程设置不同的优先级,那么ThreadX抢占阈值机制可以防止这些额外的上下文切换。这是一个重要的特性,因为在线程调度期间,它允许几个不同的线程优先级,同时消除了线程执行期间发生的一些不需要的上下文切换。

线程创建

创建线程时,需要指定几个参数:

  • 线程控制块(TCB):每个线程都必须有一个TCB,其中包含对该线程的内部处理至关重要的系统信息。但大多数应用程序不需要访问TCB的内容。
  • 线程名称:每个线程都分配了一个名称,主要用于标识目的。
  • 线程入口函数:线程的实际C代码所在的位置。
  • 线程入口输入:在线程入口函数第一次执行时传递给它的值,完全由开发人员决定。
  • 堆栈指针和大小:每个线程都必须有一个堆栈,因此指定了一个指向实际堆栈位置的指针,以及堆栈大小。
  • 线程优先级:必须提前设置优先级,但可以在运行时更改它。
  • 抢占阈值:可选项,若与优先级的值相等,则表示禁用抢占阈值特性。
  • 时间片:可选项,指定相同优先级的就绪线程之间的,可运行的计时器节拍数。注意,使用抢占阈值则禁用时间片选项。时间片值为0也表示禁用时间片选项。
  • start选项:必须指定,表示线程是立即启动,还是处于挂起状态。

堆栈分配

创建线程时,需要给每个线程分配一个堆栈(当线程被抢占时,线程可以存储信息的地方,比如返回地址和局部变量。)

每个堆栈需要一个连续字节块,它由内存字节池 memory byte pool,或内存块池 memory block pool以及数组分配,前两者都需要提前创建。

同一内存字节池/内存块池,可以为多个线程分配堆栈。

以上皆由tx_application_define函数完成。

互斥锁

一旦一个线程获得了互斥锁的所有权,它将保持所有权,直到它自愿放弃互斥锁。

不管线程的优先级如何,没有一个线程可以抢占另一个线程拥有的互斥锁。这是提供线程间互斥的一个重要特性。

因此,低优先级线程应尽量避免长时间占用互斥锁。

线程优先级

ThreadX提供了一种灵活的动态优先级分配方法。尽管每个线程必须有一个优先级,但ThreadX对如何使用优先级没有任何限制。作为一种极端情况,所有线程都可以被分配相同的优先级,而不会发生改变。但是,在大多数情况下,优先级值的分配和修改只是为了反映线程处理过程中重要性的变化。

ThreadX提供了从0-31的优先级值,其中值0表示最高优先级,值31表示最低优先级。

优先级反转

当两个具有不同优先级的线程共享一个公共资源时,可能会出现优先级反转的情况:

当低优先级线程获取了高优先级线程所需的资源(如上节互斥锁),导致高优先级线程挂起时,就会出现这种情况。如果这些线程是唯一活动的线程,则优先级反转时间受低优先级线程占用资源的时间限制。这种情况是确定的,也是很正常的。

如果一个或多个中等优先级的线程在此优先级反转条件期间处于活动状态(因此抢占了较低优先级的线程),则低优先级保有资源的时间将会延长,优先级反转时间将变得不确定,应用程序可能会失败。

优先级继承和抢占阈值,可以解决优先级反转的不确定问题。

优先级继承

在优先级继承中,一个低优先级的线程暂时获得一个高优先级的线程的优先级,而高优先级的线程正在试图获得由低优先级线程拥有的相同的互斥锁。如此可以避免低优先级线程被中等优先级线程抢占。

当低优先级的线程释放互斥锁时,它的原始优先级将被恢复,高优先级的线程将获得互斥锁的所有权。

这个特性通过将反转时间限制在低优先级线程持有互斥锁的时间,从而消除了优先级反转。注意,优先级继承只对互斥量有效,而对计数信号量无效。

抢占阈值

抢占阈值 Preemption-Threshold, 是ThreadX特有的特性。创建线程时,开发人员可以为禁用抢占指定优先级上限。这意味着优先级大于指定上限的线程仍然允许抢占,但是优先级等于或小于指定上限的线程不允许抢占。抢占阈值可以在线程执行期间的任何时候被修改。

例如,一个线程优先级值20和抢占阈值15,只有优先级高于15(即0到14)的线程才被允许抢占该线程。即使优先级15到19高于线程的优先级20,但仍不被允许抢占该线程。

通过更改线程的抢占阈值,同样可以解决优先级反转的问题。

线程状态

ThreadX 有五个不同的线程状态:就绪、挂起、执行、终止和完成。
在这里插入图片描述
当一个线程准备好执行时,它会被放在就绪线程列表中。当ThreadX调度一个线程执行时,它会选择并删除就绪线程列表中具有最高优先级的线程。如果列表中的所有线程具有相同的优先级,ThreadX将选择等待时间最长的线程,并采用时间片轮转的调度方式。

数据结构

TCB

在ThreadX中,TCB为结构体TX_THREAD_STRUCT

通常开发人员不需要知道TCB的细节。但某些情况下特别是调试时,检查某些字段非常有用。如:

  • tx_run_count:调度线程的次数。计数器增加表示正在调度和执行线程。
  • tx_state:线程的状态。下面的列表代表了可能的线程状态:
    • TX_READY
    • TX_COMPLETED
    • TX_TERMINATED
    • TX_SUSPENDED
    • TX_SLEEP
    • TX_QUEUE_SUSP
    • TX_SEMAPHORE_SUSP
    • TX_EVENT_FLAG
    • TX_BLOCK_MEMORY
    • TX_BYTE_MEMORY
    • TX_MUTEX_SUSP
    • TX_IO_DRIVER

TCB中还有许多其他有用的字段,包括堆栈指针、时间片值和优先级。开发人员可以检查TCB的成员,但严禁修改它们。

没有显式的值来指示线程当前是否正在执行。在给定的时间只有一个线程执行,ThreadX在其他地方跟踪当前执行的线程。

注意,正在执行的线程的tx_state的值是TX_READY。

就绪列表

就绪列表其实是一个容量为32(表示32个优先级)的链表数组,数组的每个元素都是线程TCB链表头,存储该优先级已经就绪的线程TCB结点。
在这里插入图片描述
该数据结构由common/inc/tx_thread.h文件定义:

THREAD_DECLARE  TX_THREAD *     _tx_thread_priority_list[TX_MAX_PRIORITIES];

除此之外,还定义了一个ULONG类型的变量,作为每个优先级是否有线程就绪的标志。
在这里插入图片描述
如上图,ULONG变量一共32位,每位表示一个优先级,若该位为1,则表示此优先级有就绪线程。

该数据结构由common/inc/tx_thread.h文件定义:

THREAD_DECLARE  ULONG           _tx_thread_priority_maps[TX_MAX_PRIORITIES/32];

API

1.tx_thread_ create

UINT  tx_thread_create(TX_THREAD *thread_ptr, 
						CHAR *name_ptr, 
						VOID (*entry_function)(ULONG id), 
						ULONG entry_input,
						VOID *stack_start, 
						ULONG stack_size, 
						UINT priority, 
						UINT preempt_threshold,
						ULONG time_slice, 
						UINT auto_start)

创建线程:

  • 第 1 个参数 thread_ptr 是线程控制块地址。
  • 第 2 个参数 name_ptr 是线程名,这个参数主要是用于调试目的,调试的时候方便看是哪个线程。
  • 第 3 个参数 entry_function 是线程函数地址。当线程从此入口函数返回时,它将处于 Complete
    -State 完成态,并无限挂起。
  • 第 4 个参数 entry_input 是传递给线程的形参。
  • 第 5 个参数 stack_start 栈基地址。
  • 第 6 个参数 stack_size 是栈大小,单位字节,主要被函数嵌套调用和局部变量使用。
  • 第 7 个参数 priority 是线程优先级,范围 0 到(TX_MAX_PRIORITES-1),其中 0 表示最高优先级。
  • 第 8 个参数 preempt_threshold 是抢占阀值,范围 0 到(TX_MAX_PRIORITES-1)。只有高于此
    级别的优先级才可以抢占该线程。此数值必须小于或等于该线程的优先级数值。如果设置为等于该
    线程的优先级数值,将禁用抢占阈值。
  • 第 9 个参数 time_slice 是时间片大小。
  • 第 10 个参数 auto_start 是指定线程是立即启动还是处于挂起状态。
    • TX_AUTO_START(0x01)立即启动。
    • TX_DONT_START(0x00)挂起状态。
      如果指定了 TX_DONT_START,则应用程序以后必须调用 tx_thread_resume 才能运行线程。
  • 返回值:
    • TX_SUCCESS(0x00)成功创建线程。
    • TX_THREAD_ERROR(0x0E)无效的线程控制块指针。指针为 NULL 或任务已创建。
    • TX_PTR_ERROR(0x03)线程函数地址或栈的起始地址无效,通常为 NULL。
    • TX_SIZE_ERROR(0x05)栈区域的大小无效。线程必须至少具有 TX_MINIMUM_STACK 字节
      才能执行。
    • TX_PRIORITY_ERROR(0x0F)无效的线程优先级,该值超出(0 到(TX_MAX_PRIORITIES-1))
      范围。
    • TX_THRESH_ERROR(0x18)指定了无效的抢占阈值。该值的有效优先级必须小于或等于该线程的初始优先级数值。
    • TX_START_ERROR(0x10)无效的 auto_start 参数。
    • TX_CALLER_ERROR(0x13)无效调用。
  • 不允许在中断服务程序中调用,只可以在初始化和线程中调用。
  • 使用抢占阈值将禁用时间片。合法的时间片值范围是 1 到 0xFFFFFFFF(包括 0)。值为
    TX_NO_TIME_SLICE(值为 0)将禁用此线程的时间片。
  • 使用时间分片会导致少量系统开销。由于时间片仅在多个线程共享相同优先级的情况下才有用,因此,具有唯一优先级的线程不要分配时间片。

2.tx_thread_delete

UINT tx_thread_delete(TX_THREAD *thread_ptr)

删除线程:

  • 第 1 个参数填要删除的线程控制块地址。
  • 返回值:
    • TX_SUCCESS(0x00)成功删除线程。
    • TX_THREAD_ERROR(0x0E)无效的TCB指针。
    • TX_DELETE_ERROR(0x11)指定的线程未处于 Terminated 终止态或者 Completed 完成态。
    • TX_CALLER_ERROR(0x13)无效调用者。
  • 不允许在中断服务程序中调用,只可以在线程和定时器组中调用。
  • 只能删除处于 Terminated 终止态或者 Completed 完成态的线程。因此也就不可以在要删除的线程
    中调用此函数,也就是自己删除自己。

3.tx_thread_identify

#define tx_thread_identify   _tx_thread_identify

TX_THREAD  *_tx_thread_identify(VOID)

返回正在执行线程:

  • 返回值:指向当前执行线程的指针。如果没有线程正在执行,返回空指针。如果从ISR调用该服务,则返回值表示在执行中断处理程序之前正在运行的线程。

4.tx_thread_info_get

UINT  tx_thread_info_get( TX_THREAD *thread_ptr, 
							CHAR **name, 
							UINT *state, 
							ULONG *run_count, 
							UINT *priority, 
							UINT *preemption_threshold, 
							ULONG *time_slice, 
							TX_THREAD **next_thread, 
							TX_THREAD **next_suspended_thread)

获取线程信息:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 第 2 个参数 name 是线程名字符串,获取后存储的指针。
  • 第 3 个参数 state 是线程状态,获取后存储的指针。
  • 第 4 个参数 run_count 是线程调度次数,获取后存储的指针。
  • 第 5 个参数 priority 是线程优先级,获取后存储的指针。
  • 第 6 个参数 preemption_threshold 是线程抢占阈值,获取后存储的指针。
  • 第 7 个参数 time_slice 是线程时间片,获取后存储的指针。
  • 第 8 个参数 next_thread 是目标线程下一个创建的线程的TCB指针,获取后存储的指针。
  • 第 9 个参数 next_suspended_thread 是挂起列表中的下一个线程TCB指针,获取后存储的指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功获得线程信息。
    • TX_THREAD_ERROR(0x0E)无效的TCB指针。

5.tx_thread_preemption_change

UINT  tx_thread_preemption_change(  TX_THREAD *thread_ptr, 
									UINT new_threshold, 
									UINT *old_threshold)

更改线程抢占阈值:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 第 2 个参数 new_threshold 新抢占阈值。
  • 第 3 个参数 old_threshold 原抢占阈值的存储指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功更改线程抢占阈值。
    • TX_THRESH_ERROR(0x18)新抢占阈值大于优先级值。

6.tx_thread_priority_change

UINT  tx_thread_priority_change(TX_THREAD *thread_ptr, 
								UINT new_priority, 
								UINT *old_priority)

更改线程优先级:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 第 2 个参数 new_priority新优先级。
  • 第 3 个参数 old_priority原优先级的存储指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功更改线程优先级。
  • 允许在线程和定时器组里面调用。
  • 如果要同时更改抢占阈值,则必须在更改优先级完成后再更改抢占阈值。

7.tx_thread_relinquish

VOID  tx_thread_relinquish(VOID)

转让CPU控制权:

线程可以通过tx_thread_ relinquish自愿地将控制权让给另一个线程。

执行此操作通常是为了实现一种循环调度形式。这个动作是当前正在执行的线程发出的一个协作调用,它暂时放弃对处理器的控制,从而允许具有相同或更高优先级的其他线程执行。

这种技术有时称为协作多线程。

8.tx_thread_resume

UINT  tx_thread_resume(TX_THREAD *thread_ptr)

唤醒线程:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功的唤醒线程。
    • TX_SUSPEND_LIFTED(0x19)先前设置的延迟暂停已取消。
    • TX_THREAD_ERROR(0x0E)无效的线程TCB地址。
    • TX_RESUME_ERROR(0x12)指定的线程没有被挂起,或者之前被 tx_thread_suspend 以外的服务挂起。

允许在中断,线程,定时器组和初始化中调用。

9.tx_thread_sleep

UINT tx_thread_sleep(ULONG timer_ticks)

线程延迟:

  • 第 1 个参数 timer_ticks 是设置线程延迟的时钟节拍数。
  • 返回值:
    • TX_SUCCESS(0x00)成功延迟。
    • TX_WAIT_ABORTED (0x1A) ,被其它中断,定时器组或者线程终止运行。
    • TX_CALLER_ERROR (0x13) ,不是在线程中调用。

不允许在中断中调用,仅可以在线程中调用

10.tx_thread_suspend

UINT tx_thread_suspend(TX_THREAD *thread_ptr)

线程无条件挂起:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功挂起线程。
    • TX_THREAD_ERROR(0x0E)无效的线程TCB指针。
    • TX_SUSPEND_ERROR(0x14)指定的线程处于终止或完成状态。
    • TX_CALLER_ERROR(0x13)无效调用者。
  • 允许在中断,线程,定时器组和初始化中调用。
  • 如果线程已有条件挂机,则本次无条件挂起将被保存,直到有条件挂起恢复,再执行线程的无条件挂起。若线程已无条件挂起,则请求无效。

11.tx_thread_terminate

UINT tx_thread_terminate(TX_THREAD *thread_ptr)

线程无条件终止:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功终止线程。
    • TX_THREAD_ERROR(0x0E)无效的线程TCB指针。
    • TX_CALLER_ERROR(0x13)无效调用者。
  • 不允许在中断中调用,仅可以在线程和定时器组中调用,可以终止自身。
  • 终止后,必须调用函数 tx_thread_reset 复位线程以使其再次执行。
  • 应用程序有责任确保线程处于适合终止的状态。例如,线程不应在关键应用程序处理期间或在其他
    中间件组件内部终止,否则可能会使这种处理处于未知状态。

12.tx_thread_time_slice_change

UINT  _tx_thread_time_slice_change( TX_THREAD *thread_ptr, 
									ULONG new_time_slice, 
									ULONG *old_time_slice)

更改线程时间片:

  • 第 1 个参数 thread_ptr 是目标线程的TCB指针。
  • 第 2 个参数 new_time_slice新时间片。
  • 第 3 个参数 old_time_slice原时间片的存储指针。
  • 返回值:
    • TX_SUCCESS(0x00)成功。
    • TX_THREAD_ERROR:(0x0E)无效的应用程序线程指针。
    • TX_PTR_ERROR: (0x03)指向以前的时间片存储位置的指针无效。
    • TX_CALLER_ERROR:(0x13)无效的服务调用者。
  • 如果已经指定了抢占阈值,那么将禁用该线程的时间片。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43739110/article/details/117650263

智能推荐

docker中mysql容器自动停止原因及解决方法_为什么我部署在docker里面的mysql,会被关闭-程序员宅基地

文章浏览阅读6.5k次,点赞3次,收藏23次。docker update -m 400M --memory-reservation 400M --memory-swap 500M 容器名。将docker.cnf 复制到mysql容器内(容器名可用docker ps -a 查看到name列,即为容器名)sudo docker cp ./docker.cnf 容器名:/etc/mysql/conf.d。第五步:限制mysql内存占用(本机器内存为1G,可视自己的机器内容设置)查看设置内容后docker容器内存使用情况:docker stats。_为什么我部署在docker里面的mysql,会被关闭

【K8S系列】深入解析K8S存储-程序员宅基地

文章浏览阅读2.3w次,点赞91次,收藏122次。在 Kubernetes 中,存储具有非常广泛的应用场景。可以根据实际需求选择适合自己的存储方案,以便更好地管理容器化应用程序中的数据和资源。本文会从以下三个方面,带你了解k8s存储:1.k8s存储类型;2.存储使用场景;3.存储使用案例

oracle 重新编译用户无效对象_oracle重新编译失效对象-程序员宅基地

文章浏览阅读1.2w次。oracle sys用户无效对象select owner,object_name, replace(object_type,' ','') object_type,to_char(created,'yyyy-mm-dd') as created,to_char(last_ddl_time,'yyyy-mm-dd') as last_ddl_time,statusfrom dba_o_oracle重新编译失效对象

【愚公系列】2023年10月 WPF控件专题 RadioButton控件详解_wpf radiobutton-程序员宅基地

文章浏览阅读5.1w次,点赞2次,收藏3次。WPF控件是Windows Presentation Foundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见的标准用户界面元素。自定义控件则允许开发人员使用XAML和C#等编程语言来创建个性化的用户界面元素。自定义控件可以根据需求提供更多的功能和自定义化选项,以及更好的用户体验。_wpf radiobutton

ES6(阮一峰)_es6 阮一峰-程序员宅基地

文章浏览阅读3.2k次。一、let 和const 命令let 命令块级作用域块级作用域与函数声明let 命令一、基本用法二、不存在变量提升:先声明再使用三、暂时性死区:先声明再赋值使用四、不允许重复声明:只要有let 就不能重复声明不允许再函数内部重新声明参数块级作用域es5没有块级作用域,var 声明会:1.内层变量可能会覆盖外层变量;2.用来计数的循环变量泄露为全局变量。块级作用域的作用:外层代码块不受内层代码块的影响。ES6 允许块级作用域的任意嵌套。内层作用域可以定义外层作用域的同名变量。匿名立_es6 阮一峰

Vue + Axios + ASP.NET Core WebAPI + 文件上传下载_axios filecontentresult-程序员宅基地

文章浏览阅读1.8k次。准备文件上传的API#region 文件上传 可以带参数[HttpPost(“upload”)]public JsonResult uploadProject(IFormFile file, string userId){if (file != null){var fileDir = “D:\aaa”;if (!Directory.Exists(fileDir)){Directory.CreateDirectory(fileDir);}//文件名称string projectFi_axios filecontentresult

随便推点

Dijkstra算法_dijkstra 采用邻接表 在一个边上带权网络中,求给定的两个顶点之间的最短路径。 编-程序员宅基地

文章浏览阅读1.4k次。Dijkstra算法是求解带权有向图中两点之间最短路径的经典算法,它采用贪心策略,维护一个向量dis用来表示从起点到其余顶点的路径“长度”。其主要原理如下:1.定义一个向量dis[],下标表示顶点,即dis[v]的值表示为从起点到v点的已知最短路径长度。初始化向量时,若v不是起点的相邻顶点,则dis[v]=∞,起点dis[0]=0。2.定义向量edge[],下标和值都表示顶点,假设edge..._dijkstra 采用邻接表 在一个边上带权网络中,求给定的两个顶点之间的最短路径。 编

吃了这些数据集和模型,跟 AI 学跳舞,做 TensorFlowBoys|湾区人工智能-程序员宅基地

文章浏览阅读669次。By 超神经场景描述:利用深度学习算法 GAN 可实现动作追踪与迁移,将某人物动作复制到其他人,应用到舞蹈领域,人人皆可成舞王。关键词:GAN 动作迁移 舞蹈最近,《..._街舞ai模型

全连接网络手写数字识别(极详细,互助)_基于全连接网络的手写数字识别-程序员宅基地

文章浏览阅读2.1k次。文章目录全连接网络手写数字识别定义类:创建数据集Dataset类\_\_init\_\_函数**\_\_getitem\_\_**与\_\_len\_\_make_txt_file 函数测试定义类:全连接网络super(HandWritingNumberRecognize_Network, self).\_\_init\_\_()forward函数:网络结构训练函数optimizer.zero_grad损失函数验证函数torch.no_grad()计算正确率测试函数mainoptimizer =torch._基于全连接网络的手写数字识别

main方法运行httpclient巨多日志_httpclient一直刷日志-程序员宅基地

文章浏览阅读2.2k次,点赞5次,收藏2次。如果本地使用httpclient调用一些接口,会产生巨多的日志public static void main(String[] args) throws Exception { HttpUtil.doGet("https://www.baidu.com"); }[main] DEBUG org.apache.http.client.protocol.RequestAdd..._httpclient一直刷日志

java关于数组遍历的理解案例_java创建一个数组可以遍历里面内容数组是整数-程序员宅基地

文章浏览阅读267次。数组遍历案例for循环遍历数组:public class continuenotes { public static void main(String[] args) { // TODO Auto-generated method stub int[] arr = {1,2,3,4,5,6};//定义数组 //使用for循环遍历数组的元素 for(int i = 0;i<arr.length;i++) { System.out.println(arr[i]); } _java创建一个数组可以遍历里面内容数组是整数

2021运动蓝牙耳机,性价比高的四款运动蓝牙耳机_2021年运动蓝牙耳机推荐-程序员宅基地

文章浏览阅读101次。2021运动蓝牙耳机,性价比高的四款运动蓝牙耳机近几年陆陆续续发布了不少的蓝牙耳机,现在整个蓝牙耳机市场,从几十到上千的耳机应有尽有,不过由于竞争激烈,也并不一定是高价的耳机一定好,低价的耳机一定差,在低价耳机中也涌现出了不少配置较高的耳机,今天就在这里推荐一些性价比高的蓝牙耳机。一、Nank南卡lite Pro运动蓝牙耳机价格:399防水等级:5蓝牙版本:5.2NANK南卡LITE创新采用了双主机+高通5.2芯片+NANK游戏加速模式,拥有独立芯片和完整电路,两边都是主耳机,搭_2021年运动蓝牙耳机推荐

推荐文章

热门文章

相关标签