1、多线程编程的基本概念和优势

文章介绍了并发编程的重要性,如提高响应速度、代码模块化以及充分利用CPU资源。讨论了进程和线程的基本概念,包括进程的实例化、线程作为CPU调度单位的角色。还提到了Java中的线程应用、进程间通信方式,并探讨了CPU核心数与线程数的关系、上下文切换的成本以及并行和并发的区别。此外,文章还强调了并发的三大特性:可见性、有序性和原子性,并提供了相关保证机制。
摘要由CSDN通过智能技术生成

多线程编程的基本概念和优势

一、为什么我们要学习并发编程?

并发编程和性能优化是密切相关的,使用并发编程可以做到:

1)、加快响应用户的时间:

2)、使你的代码模块化,异步化,简单化

		例如我们实现电商系统,下订单和给用户发送短信、邮件就可以进行拆分,
		
	将给用户发送短信、邮件这两个步骤独立为单独的模块,并交给其他线程去执行。
	
	这样既增加了异步的操作,提升了系统性能,又使程序模块化,清晰化和简单化

3)、充分利用 CPU 的资源
		多核下如果还是使用单线程的技术做思路明显就 out 了,无法充分利用 CPU的
		
	多核特点。如果设计一个多线程的程序的话,那它就可以同时在多个 CPU 的多个核的
	
	多个线程上跑,可以充分地利用 CPU,减少 CPU 的空闲时间,发挥它的运算能力,提高
	
	并发量。

二、基础概念

在正式学习 Java 的并发编程之前,还有几个并发编程的基础概念我们需要熟悉和学习。

1、进程和线程

1)、进程:

		我们常听说的是应用程序,也就是 app,由指令和数据组成。但是当我们不
		
	运行一个具体的 app 时,这些应用程序就是放在磁盘(也包括 U 盘、远程网络
	
	存储等等)上的一些二进制的代码。一旦我们运行这些应用程序,指令要运行,
	
	数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中
	
	还需要用到磁盘、网络等设备,从这种角度来说,进程就是用来加载指令、管理
	
	内存、管理 IO 的。

		当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程;

		进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程
		
	(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如
	
	网易云音乐、360 安全卫士等)。显然,程序是死的、静态的,进程是活的、动态
	
	的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进
	
	程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由
	
	你启动的进程

	站在操作系统的角度,进程是程序运行资源分配(以内存为主)的最小单位;
	

2)、线程:
	一个机器中肯定会运行很多的程序,CPU 又是有限的,怎么让有限的 CPU运行
	
这么多程序呢?就需要一种机制在程序之间进行协调,也就所谓 CPU 调度。线程则

是 CPU 调度的最小单位。

	线程必须依赖于进程而存在,线程是进程中的一个实体,是 CPU 调度和分派的
	
基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统

资源,,只拥有在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可

与同属一个进程的其他的线程共享进程所拥有的全部资源。一个进程可以拥有多个线

程,一个线程必须有一个父进程。线程,有时也被称为轻量级进程(Lightweight 

Process,LWP),早期 Linux 的线程实现几乎就是复用的进程,后来才独立出自己

的 API。

2、Java 线程的无处不在

	Java 中不管任何程序都必须启动一个 main 函数的主线程; Java Web 开发里
	
面的定时任务、定时器、JSP 和 Servlet、异步消息处理机制,远程访问接口 RM 等,

任何一个监听事件,onclick 的触发事件等都离不开线程和并发的知识

3、进程间的通信

1、同一台计算机的进程通信称为 IPC(Inter-process communication),不同计

算机之间的进程通信被称为 R(mote)PC,需要通过网络,并遵守共同的协议,比如大

家熟悉的 Dubbo 就是一个 RPC 框架,而 Http 协议也经常用在 RPC 上,比如: 

SpringCloud 微服务

2、进程间通信有几种方式?
	1)、管道:
			分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用于
			
		具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外,
		
		它还允许无亲缘关系进程间的通信。
		
	2)、信号(signal):
			信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,
			
		用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请
		
		求效果上可以说是一致的。
		
	3)、消息队列(message queue):
			消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,
			
		具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有
		
		读权限得进程则可以从消息队列中读取信息
		
	4)、共享内存(shared memory):
			可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,
			
		不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种
		
		同步操作,如互斥锁和信号量等。
		
	5)、信号量(semaphore):
			主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
			
	6)、套接字(socket):
			这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间
			
		通信,应用非常广泛。同一机器中的进程还可以使用Unix domain socket(比如同
		
		一机器中 MySQL 中的控制台 mysql shell 和 MySQL 服务程序的连接),这种方
		
		式不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,比纯
		
		粹基于网络的进程间通信肯定效率更高

4、CPU 核心数和线程数的关系

	前面说过,目前主流 CPU 都是多核的,线程是 CPU 调度的最小单位。同一
	
时刻,一个 CPU 核心只能运行一个线程,也就是 CPU 内核和同时运行的线程数

是 1:1 的关系,也就是说 8 核 CPU 同时可以执行 8 个线程的代码。但 Intel 

引入超线程技术后,产生了逻辑处理器的概念,使核心数与线程数形成 1:2 的关

系。内核数是 6 ,逻辑处理器数是 12。

	在 Java 中提供了 Runtime.getRuntime().availableProcessors(),可以
	
让我们获取当前的 CPU 核心数,注意这个核心数指的是逻辑处理器数。

	获得当前的 CPU 核心数在并发编程中很重要,并发编程下的性能优化往往和 CPU
	
 核心数密切相关。

5、上下文切换(Context switch)

		既然操作系统要在多个进程(线程)之间进行调度,而每个线程在使用 CPU
		
	时总是要使用 CPU 中的资源,比如 CPU 寄存器和程序计数器。这就意味着,操
	
	作系统要保证线程在调度前后的正常执行,所以,操作系统中就有上下文切换的概
	
	念,它是指 CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换

	上下文是 CPU 寄存器和程序计数器在任何时间点的内容;

	寄存器是 CPU 内部的一小部分非常快的内存(相对于 CPU 内部的缓存和 CPU
	
外部较慢的 RAM 主内存),它通过提供对常用值的快速访问来加快计算机程序的执行。	

	程序计数器是一种专门的寄存器,它指示 CPU 在其指令序列中的位置,并保存着
	
正在执行的指令的地址或下一条要执行的指令的地址,这取决于具体的系统。


	上下文切换可以更详细地描述为内核(即操作系统的核心)对 CPU 上的进程(包括线程)
	
执行以下活动:

	1. 暂停一个进程的处理,并将该进程的 CPU 状态(即上下文)存储在内存中的某个地方
	
	2.  从内存中获取下一个进程的上下文,并在 CPU 的寄存器中恢复它
	
	3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。


	从数据来说,以程序员的角度来看, 是方法调用过程中的各种局部的变量与资源; 
	
以线程的角度来看, 是方法的调用栈中存储的各类信息。


	引发上下文切换的原因一般包括:线程、进程切换、系统调用等等。上下文切换通
	
常是计算密集型的,因为涉及一系列数据在各种寄存器、 缓存中的来回拷贝。就 CPU 

时间而言,一次上下文切换大概需要 5000~20000 个时钟周期,相对一个简单指令几个

乃至十几个左右的执行时钟周期,可以看出这个成本的巨大。

6、并行和并发

6.1、并行
	指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

在这里插入图片描述

	我们举个例子,如果有条高速公路 A 上面并排有 8 条车道,那么最大的并行车
	
辆就是 8 辆此条高速公路 A 同时并排行走的车辆小于等于 8 辆的时候,车辆就可

以并行运行。CPU 也是这个原理,一个 CPU 相当于一个高速公路 A,核心数或者线

程数就相当于并排可以通行的车道;而多个 CPU 就相当于并排有多条高速公路,而

每个高速公路并排有多个车道。
6.2、并发
	指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在
宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分
成若干段,使多个进程快速交替的执行;

	当谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少?
离开了单位时间其实是没有意义的。

在这里插入图片描述

并发三大特性:
6.2.1、可见性:
	当一个线程修改了共享变量的值,其他线程能够看到修改的值。Java 内存模型是
通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主
内存作为传递媒介的方法来实现可见性的。

如何保证可见性:
	1)、通过 volatile 关键字保证可见性
	2)、通过 内存屏障保证可见性
	3)、通过 synchronized 关键字保证可见性
	4)、通过 Lock保证可见性
	5)、通过 final 关键字保证可见性

我们通过下面的Java小程序来分析Java的多线程可见性的问题:

/**
 * @author Fox
 *
 * ‐XX:+UnlockDiagnosticVMOptions ‐XX:+PrintAssembly ‐Xcomp
 */
public class VisibilityTest {
	private boolean flag = true;
	public void refresh() {
		flag = false;
		System.out.println(Thread.currentThread().getName() + "修改flag");
	}

	public void load() {
		System.out.println(Thread.currentThread().getName() + "开始执行.....");
		int i = 0;
		while (flag) {
			i++;
			//TODO 业务逻辑
		}
		System.out.println(Thread.currentThread().getName() + "跳出循环: i=" + i);
	}

	public static void main(String[] args) throws InterruptedException {
		VisibilityTest test = new VisibilityTest();
		// 线程threadA模拟数据加载场景
		Thread threadA = new Thread(()> test.load(), "threadA");
		threadA.start();
		// 让threadA执行一会儿
		Thread.sleep(1000);
		// 线程threadB通过flag控制threadA的执行时间
		Thread threadB = new Thread(()> test.refresh(), "threadB");
		threadB.start();
	}

	public static void shortWait(long interval) {
		long start = System.nanoTime();
		long end;
		do {
			end = System.nanoTime();
		} while (start + interval >= end);
		}
	}

思考:上面例子中为什么多线程对共享变量的操作存在可见性问题?

6.2.2、有序性:
即程序执行的顺序按照代码的先后顺序执行。JVM 存在指令重排,所以存在有序性问题

如何保证有序性:
	1)、通过 volatile 关键字保证有序性
	2)、通过 内存屏障保证有序性
	3)、通过 synchronized关键字保证有序性
	4)、通过 Lock保证有序性
6.2.3、 原子性:
	一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。
在Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。不
采取任何的原子性保障措施的自增操作并不是原子性的。

如何保证原子性:
	1)、通过 synchronized 关键字保证原子性
	2)、通过 Lock保证原子性
	3)、通过 CAS保证原子性
6.3、综合来说:
	并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单
处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只
是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)
并发 Concurrent:指应用能够交替执行不同的任务,比如单 CPU 核心下执行多
	
	线程并非是同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的
速度不断去切换这两个任务,已达到"同时执行效果",其实并不是的,只是计算机的速度
太快,我们无法察觉到而已.


	并行 Parallel:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打
	
电话,这两件事情可以同时执行

	两者区别:一个是交替执行,一个是同时执行,如下图所示。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值