初识多线程
Posted caiyec
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识多线程相关的知识,希望对你有一定的参考价值。
初识多线程
前言
我们先简单介绍操作系统来进一步了解多线程
一、操作系统
1.冯诺依曼体系结构
冯诺依曼体系结构是由CPU(运算器,控制器) 存储器 输入设备 和输出设备组成
CPU(运算器,控制器): 算术运算和逻辑判断
存储器:主要功能是存储设备 分为内存和外存 内存一般空间比较小,但是访问速度快,内存空间大,但是访问速度慢
输入输出设备输入设备 键盘鼠标 输出设备 显示器,打印机等
简单描述CPU是如何工作的:
CPU的核心功能就是执行一些指令,指令就是一些二进制的数据,用来表示一些特定的含义,例如我们写的java代码先被编译器编译为字节码之后被JVM翻译为成一条条的CPU指令,最终在CPU上执行,而 C++代码直接把源代码就编译成二进制的机器指令了,Java/C++ 代码在编译好之后就得到了一些二进制的机器指令,这些机器指令是保存在硬盘上的,当我们进行运行的时候,这时候操作系统先把这些指令从磁盘加载到内存中,再由CPU从内存中读取指令进行执行
2.操作系统
操作系统是一个软件,一个管理的软件,管理硬件设备(CPU,内存,磁盘,鼠标等)和软件资源(进程,线程,内核中特殊的资源),操作系统是计算机最重要的软件。
操作系统在其中运筹帷握,才能让这些硬件和软件能够很好的搭配工作
当前主流的操作系统 PC端 Windows Linux Mac 移动端 IOS Android 鸿蒙
二、进程
进程(Process)操作系统中的核心概念,也和平时运行程序密切相关,进程是按照一定的流程(代码中所编写的CPU指令)来具体执行一些计算工作,通俗来说:当运行某个程序的时候,操作系统就创建了一个对应的进程
进程的定义:
1)进程是程序的动态执行
2)进程是一个程序及其数据在处理机上顺序执行时所发生的活动
3)进程是具有独立功能的程序在其数据集合上运行的过程,他是系统调度和资源分配的一个独立单位
我们可以通过任务管理器来查看当前电脑上由多少进程,
为什么引入进程:
因为现代操作系统,都需要支持“多任务”“并发” 执行,多任务并发执行能更充分利用多核资源,还有更充分利用CPU资源
1)系统支持多任务
2)更充分利用CPU资源
2.1操作系统管理进程
描述:使用task struct 结构
进程的组成方式:使用双向链表把很多的task struct 变量给串起来(例如,当我们看到任务管理器可以认为,是操作系统内核遍历了双向链表,然后把每个节点的信息获取出来,并展示)
当我们创建一个进程时,实际上就是创建一个task struct 放到双向链表中,当进程结束了就是从双向链表中删除该节点。
2.2进程的组成
一个进程的task struct 里面大概都有啥样的信息
1)pid 进程ID 在任务管理器中也能看到
2)进程的内存指针:描述了进程持有的内存资源是哪些范围(进程依赖的代码和数据在哪里)
下面的信息进程的组成为了辅助进行进程的“调度”
调度 如果在系统某一刻可能有100个进程,我们应该如何执行,
并行从微观角度,每个进程和进程之间,是同时进行的,比如 8个CPU核心,可以同时进行8个进程
并发从微观角度讲,进程是串行执行的,从宏观角度讲,进程是“同时”进行的,
例如一个CPU负责执行3个进程,CPU先执行进程一,之后进行进程二,最后执行进程三,执行依次来回切换,由于CPU切换速度极快,从宏观角度来看好像是三个进程同时执行,但是微观角度依然是串行执行的。
3)进程的优先级多个进程存在,哪个进程要先执行
4)进程的上下文记录一次活动的相关状态(某个进程在CPU上执行一段时间之后,就需要调度下去了,这时进程就需要把上次执行的状态给记录下来)记录的内容:当时CPU的各个寄存器的值,记录到内存中 记录的目的,下次运行的时候,就能够恢复上次的信息,继续向下执行
5)进程的记账信息:统计工具(每个进程在CPU上执行多久了,调度多少次了)
6)进程的状态当前进程的状态,影响调度
进程的调度
所谓的进程调度,其实就是一个抢占式执行的过程,这里的调度工作完全是由操作系统内核来实现,每个进程自身,不知道自己啥时候能获取到CPU的执行,也不知道自己啥时候失去CPU,更不知道下一次获取到CPU是啥时候,一切都是听操作系统的,一切对于应用程序来说都是透明的。
2.3时间片
现代操作系统都支持多任务,就是操作系统可以同时运行多个任务
操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
由于CPU执行的效率非常高,时间片非常短,在各个任务之间快速切换,给人一种多个任务同时执行的感觉,这就是我们所说的并发
2.4并行和并发
并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间内,让多个进程都得以推进,称为并发
并行:多个进程在多个CPU下分别,同时进行运行,这称为并行。
2.5内核态和用户态
内核态,操作系统内核来执行任务
用户态,应用程序来执行任务
完整的操作系统=操作系统内核+配套的应用程序
如果某个操作系统进入内核操作,就会变得不可控
2.6进程状态
三种基本状态
就绪 | 已经具备运行条件,但是由于没有空闲CPU,而暂时不能运行 |
---|---|
运行 | 占有CPU,正在CPU上运行 |
堵塞 | 等待某一事件的发生,暂时不能运行 |
另外两种状态
创建 | 进程正在被创建,操作系统为进程分配资源,初始化PCB |
---|---|
结束 | 进程正在从系统中撤销,操作系统会回收进程拥有的资源,撤销PCB |
三、多线程
3.1线程是什么?
进程是为了实现并发编程的效果,但是为了追求效率就引入了线程(创建一个进程/销毁一个进程,开销比较大)因此就希望能够更高效,更轻量的完成并发编程。通过线程来完成,线程也被称为“轻量级进程”
每个线程就对应到一个“独立的执行流”在这个执行流里就能够完成一系列的指令,多个线程,就有了多个执行流,就可以并发的完成多个系列的指令了。
进程和线程的关系:
一个进程包含了多个线程
一个进程其实从系统这里申请了很多的系统资源~~,进程同一对这些资源进行管理,这个进程内的多个线程,共享了这些资源
进程具有独立性:一个进程掉了,不会影响到其他进程
线程则不是这样,如果一个线程坏了,其他的线程可能受到影响。
举个例子来说明线程和进程
如果我们要吃100个西瓜
如果是引入多进程的方式就是
但是当我们创建新的进程时利用系统大量的开销,也不太高效
这样我们就引入多线程,把单线程变为多线程
在这种情况下,同样可以达到并发编程,调度器也是可能会把两个线程安排到不同的CPU上,开销比多进程更低
但是线程越多越好吗? 我们引入100个人吃西瓜,每个人只吃一个,岂不是更快?
所以增加线程可能会提高效率,但是一旦线程数量太多,就会拥挤不堪,这个时候,多个线程为了竞争CPU资源就会占更多的开销(线程多了,调度的开销也变大了)
3.2进程和线程的区别
1)进程时包含线程的~一个进程可以包含一个线程,也可以包含多个线程。
2)进程是资源分配的基本单位,线程是系统调度执行的基本单位
3)进程和进程之间是相互独立的,进程A挂了,不会影响进程B,同一个进程的若干线程之间,共享这一资源(内存资源),如果某个线程出现异常,可能会导致整个进程终止,因此其他线程也就无法工作了。
3.3Java实现多线程
继承Thread 重写run 方法
public class Demo3 {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
myThread1.start();
while (true){
System.out.println("这是主线程!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread1 extends Thread{
@Override
public void run() {
//线程要执行的任意方法
while(true){
System.out.println("这是新线程!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
多线程创建:
1)创建一个类继承Thread
2)重写Thread 的run方法 ,在新的run里面编写线程要执行的执行流,
3)创建子类实例
4)调用子类的start 方法.
3.4通过代码演示多线程提高效率
1.先不使用多线程,直接串行执行
public class Demo4 {
//演绎多线程来提高程序的运行效率
//假设把两个long类型的整数,给自增100亿次
//1.先不使用多线程,直接串行执行
private static long count=100_0000_0000L;
public static void serial(){
long start=System.currentTimeMillis();//毫秒级时间戳
long a=0;
for(int i=0;i<count;i++){
a++;
}
long b=0;
for(int i=0;i<count;i++){
b++;
}
long end=System.currentTimeMillis();
System.out.println("总执行时间"+(end-start)+"ms");
}
public static void main(String[] args) {
serial();
}
}
执行时间是10S左右:
2.使用多线程
public class Demo4 {
//演绎多线程来提高程序的运行效率
//假设把两个long类型的整数,给自增100亿次
//1.先不使用多线程,直接串行执行
private static long count=100_0000_0000L;
public static void serial(){
long start=System.currentTimeMillis();//毫秒级时间戳
long a=0;
for(long i=0;i<count;i++){
a++;
}
long b=0;
for(long i=0;i<count;i++){
b++;
}
long end=System.currentTimeMillis();
System.out.println("总执行时间"+(end-start)+"ms");
}
public static void concurrency(){
//2.并发执行
long beg=System.currentTimeMillis();
//创建两个线程
Thread t1=new Thread(){
@Override
public void run() {
long a=0;
for(long i=0;i<count;i++){
a++;
}
}
};
t1.start();
Thread t2=new Thread(){
@Override
public void run() {
long b=0;
for(long i=0;i<count;i++){
b++;
}
}
};
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end=System.currentTimeMillis();
System.out.println("执行时间"+(end-beg)+"ms");
}
public static void main(String[] args) {
//serial();
concurrency();
}
}
时间缩短了4S,为什么创建两个线程时间不是缩短5S,因为创建出来的两个线程不能保证完全是并行执行的~
以上是关于初识多线程的主要内容,如果未能解决你的问题,请参考以下文章