一、多线程基础 - 线程简介

本文介绍了并发编程的历史发展,从早期的单进程计算到引入线程提高资源利用率和性能。线程带来了并发执行的优势,如性能提升、建模简单性和异步处理简化,但也带来了安全问题、死锁及性能开销等挑战。线程与进程、管程的概念被详细阐述,同时区分了并发、并行、同步、异步、阻塞与非阻塞等关键概念。此外,还讨论了线程同步中的消息通知和上下文切换等操作对性能的影响。
摘要由CSDN通过智能技术生成

一、简介

 

1、并发简史

阶段一:早期的计算机中不包含操作系统,它们从头到尾只能执行一个程序,并且这个程序能够访问计算机的所有资源,在这种环境下程序不仅很难编写和运行,而且对计算机资源也是一种浪费。

 

阶段二:操作系统以及进程的出现,使得计算机能够同时运行多个程序,每个程序在单独的进程中运行;操作系统为每个独立的进程分配资源,包括内存、文件句柄以及安全证书;同时进程间的相互通信可以通过一些粗粒度的通信机制来交换数据,比如:套接字、信号输出器、共享内存、信号量以及文件。

进程是资源分配的基本单位,具有以下优点:

  • 资源利用率
  • 公平性 - 不同的用户与程序对计算机上的资源具有同等的使用权
  • 便利性

 
阶段三:线程的出现 促使进程的出现的因素 (资源利用率、公平性、遍历性) 同样也促使了线程的出现;线程会共享进程范围内的共享资源,例如内存句柄和文件句柄,每个线程拥有各自的程序计数器 (Program Counter)、栈以及局部变量,同时多个线程可以调度在多个CPU处理器上运行。

线程也称之为轻量级进程,是资源调度与执行的基本单位;由于同一个进程中的多个线程共享进程的内存空间,因此这些线程都能够访问相同的变量并在同一个堆上分配对象。---- 这就需要一种比进程间共享数据更细粒度的数据共享机制,保证数据的同步。

 

2、线程的优势

线程能够提高复杂系统的性能,有效降低程序的开发和维护成本,降低代码的复杂度、使得代码更容易编写、阅读与维护。

 

2.1 发挥多处理器的强大功能

多个线程可以在多个处理器上执行,提高处理器资源的利用率来提升系统吞吐量;同时使用多线程也有助于提升单个处理器系统的吞吐率

 

2.2 建模型的简单性

对于每个线程来说只执行某一种特定的任务,无需管理不同任务之间的优先级与执行时间,以及任务的切换。

 

2.3 异步事件的简化处理

服务器应用程序在接受来自多个远程客户端的套接字连接请求时,如果为每个连接都分配其各自的线程并且使用同步I/O,那么就会降低这类程序的开发难度。(在单线程应用程序中,一旦一个线程被阻塞,其他线程都将停顿,这是就必须使用非阻塞I/O,但该I/O的复杂程度远远高于同步I/O,并且很容易出错)

 

3、线程所带来的挑战

Java对线程的支持其实是一把双刃剑。虽然Java提供了相应的语言和库,以及一种明确的跨平台内存模型(该内存模型实现了在Java中开发“编写一次,随处运行”的并发应用程序),这些工具简化了并发应用程序的开发,但同时也提高了对开发人员的技术要求。

 

3.1 安全性问题

多个线程要共享相同的内存地址空间,并且是并发运行,因此它们可能会访问或修改其他线程正在使用的变量。程会由于无法预料的数据变化而发生错误。

 

3.2 死锁问题

由于多线程情况下存在资源的竞争,此时就会产生一种情况,线程A持有了线程B的资源1,线程B持有了线程A的资源2,双方想要对方资源却又不释放对方资源,在这种情况下就会一直等待下去。

 

3.3 性能问题

频繁的上下文切换:

在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁地出现上下文切换操作(Context Switch),这种操作将带来极大的开销:保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是线程运行上。

线程的同步机制:

对于线程的共享数据,需要使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。

 
 

二、基本概念

1、进程、线程、管程

进程:

程序的一次执行过程或一个正在运行的程序,它是资源分配的单位,每个进程拥有自己的一套变量。

线程:

程序内部的一条执行路径,它是调度和执行的单位,每个线程拥有自己的局部变量表、程序计数器(指向正在执行的指令指针)以及各自的生命周期。

现代操作系统中一般不止一个线程在运行,当启动了一个Java虚拟机(JVM)时,从操作系统开始就会创建一个新的进程(JVM进程),JVM进程中将会派生或者创建很多线程。

一个进程的多个线程共享同一个内存单元,从同一个堆中分配对象,可以访问相同的变量和对象,使得线程之间的通信变得简便、高效,但同时会引发安全隐患。

管程:

即同步监视器(Monitor),在Java中每个对象都有与之相关联的同步监视器,JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的。Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。

Object o = new Object();

new Thread(() -> {
    synchronized (o)
    {

    }
},"t1").start();

 

2、并发、并行

并行:

多个cpu在同一时间点执行多个线程。例如:多个人做不同的事。同一个时间点做多件事。

并发:

一个cpu在同一个时间段内执行多个线程。例如:秒杀,多个人做同一件事。同一个时间段做多件事。

 

3、同步与异步、消息通知

同步与异步:

同步与异步描述的是被调用者的(即下文的B)。

如A调用B:

如果是同步,B在接到A的调用后,会立即执行要做的事。A的本次调用可以得到结果。

如果是异步,B在接到A的调用后,不保证会立即执行要做的事,但是保证会去做,B在做好了之后会通知A。A的本次调用得不到结果,但是B执行完之后会通知A。

消息通知:

异步的概念和同步相对。当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。实际处理这个调用的部件在完成后,通过状态、通知 和 回调来通知调用者。

这里提到执行部件和调用者通过三种途径返回结果:状态、通知和回调。使用哪一种通知机制,依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制:

  • 如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一种很严重的错误)。
  • 如果是使用通知的方式,效率则很高,因为执行部件几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。

 

4、阻塞与非阻塞

阻塞与非阻塞描述的是调用者的(下文的A)

如A调用B:

如果是阻塞,A在发出调用后,要一直等待,等着B返回结果。

如果是非阻塞,A在发出调用后,不需要等待,可以去做自己的事情。

 
同步不一定阻塞,异步也不一定非阻塞。没有必然关系。
 
老张烧水:

1 老张把水壶放到火上,一直在水壶旁等着水开。(同步阻塞)

2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)

3 老张把响水壶放到火上,一直在水壶旁等着水开。(异步阻塞)

4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)

1和2的区别是,调用方在得到返回之前所做的事情不一行。 1和3的区别是,被调用方对于烧水的处理不一样(存在 “响” 的通知,即上文提到的消息通知)。

 

参考:

Java并发编程实战

Java成神之路:http://hollischuang.gitee.io/tobetopjavaer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值