并发编程
在计算机的操作系统中,我们了解到了进程管理,有了解到了cpu的特性,核心数和频率,在次之前我们所写的代码都是只用到了一个核心,此时无论你怎么优化代码,最多也只能使用到一个cpu的核心,把这个核心跑满了,其他的核心也是闲着,所以我们可以通过特殊的编写代码,把多个CPU的核心都能利用起来,这样的代码就称"并发编程",多进程编程也是一种典型的并发编程.
线程的由来
虽然多进程能够解决问题,但是随着对于效率要求越来越高,希望有更好的方式来进行并发编程,多进程编程,最大的问题就是太重了,创建进程/销毁进程,开销比较大(时间和空间),一旦需求场景需要频繁的创建销毁线程,开销就会非常明显了,最典型的就是服务器开发,针对每个发送请求的客户端,都创建一个独立的进程,由着个进程给客户端提供服务.为了解决进程开销的问题,发明了线程(Thread)
线程
线程可以理解成更轻量级的进程,也可以解决并发编程的问题,但是创建/销毁线程比进程开销更低,因此多线程编程成为更主流的并发编程方式,
所谓的进程,在系统中,是通过PCB结构体来描述的,也是通过链表的形式连接起来的,对于系统中,线程,同样也是通过PCB来描述的(Linux)
一个进程是一组PCB,一个线程是一个PCB,存在了包含关系,一个进程中至少有一个线程,也可以有多个线程,但不能没有线程,此时,每个线程都可以,独立到CPU上调度执行,线程是系统调度执行的基本单位 ,进程是系统进行资源分配的基本单位
一个可执行程序,运行的时候(双击),操作系统就会创建线程,给这个程序分配各种系统资源(CPU,内存,硬盘,网络带宽),同时也会在这个进程中,创建出这个一个或者多个线程,这些线程再去CPU上进行调度,
如果有多个线程在一个进程中,每个线程都会有自己的状态,优先级,记账信息,上下文,每个都会各自独立在CPU上调度执行,同一个进程中的这些线程,共用同一份系统资源,线程比进程更轻量,主要在于,创建线程省去了"分配资源"的过程,销毁线程也省去了"释放资源"的过程,一旦创建进程就会创建线程,系统就会分配资源,后续创建第二个第三个线程的时候,就不会再重新分配资源了.
适当提升线程数目,可以提高效率,关键是利用多核心,进行"并行执行",如果线程数目太多,超出了CPU的核心数,就会适得其反,线程之间相互抢占资源,由于多个线程,使用的是同一份资源(内存资源)->代码中定义的对象/变量,如果多个线程对同一个变量进行读写(尤其是写),就容易引起冲突,一旦发生冲突就可能程序出问题.
当一个进程中,有多个线程的时候,一旦某个线程抛出异常,这个时候,如果能够妥善处理,还好一旦处理不当,就可能导致进程崩溃,因此其他线程也会随之崩溃了.
所谓的"核心"是CPU的核心~硬件设备,而进程是系统管理的软件资源,进程也叫任务,任务需要交给核心来执行,每个进程中又可以包含多个线程
理解并行和并发
在微观角度看,多个核心,每个核心都可以执行一个线程,这些核心之间的执行过程是"同时执行的"并行,
一个核心,"分时复用",来切换多个线程,多个线程是一个接一个的执行,由于调度速度够快,宏观是看起来好像是同时执行,称为并发执行
进程线程的概念区别
1.进程包含线程,一个进程中至少有一个线程,也可以有多个线程,但不能没有线程
2.进程是系统分配资源的基本单位,线程是系统执行调度的基本单位
3.同一个进程里的线程之间,共用同一份系统资源(内存,硬盘,网络带宽等.....)尤其是"内存资源",就是代码中定义的对象/变量,编程中,多个线程可以共用同一份变量.
4.线程是当下实现并发编程的主流方式,通过多线程,就可以充分利用好多核心CPU,但是也不是线程数目越多,越好线程数目达到一定程度,把多个核心都充分利用好了之后,此时继续增加线程,无法在提高效率,甚至可能会影响效率(线程调度,也是有开销的)
5.多个线程之间,可能会相互影响,线程安全问题,一个线程抛出异常,也可能把其他线程一起带走
6.多个进程之间,一般不会相互影响,一个进程崩溃了,不会影响到其他进程(这一点也成为"进程的隔离性")
如何在Java代码中,编写多线程程序
线程,本身是操作系统提供的概念,操作系统提供API,供程序员使用,不同的操作系统,提供的API是不同的,而Java(JVM)把这些系统API封装好了,只需了解Java提供的这一套API就行
Thread(标准库),这个类就负责完成多线程的相关开发,此处这个类可以直接使用,不需要导入任何的包,在Java中,Java.lang默认自动导入
重新run方法
随后通过调用start()方法,就会在进程内部创建出一个新的线程,新的线程就会执行刚刚run的代码,
此时没有调用run方法,但是最终被执行了,这样的方法就成为回调函数,用户手动定义了,但是没有手动调用,最终这个方法被系统/库/框架调用了
这个方法是Thread的静态方法,通过类名.方法名进行调用
从编译角度理解:静态理解成编译过程中确定的,动态理解成运行过程中确定的,除了重写的方法,动态绑定,运行时确定,不去重写,也就是静态的
前台线程:如果这个线程在执行过程中,不能阻止进程结束,(虽然线程在执行过程中,但是进程要结束了,此时线程也会随之被带走),这样的线程就叫做前台线程.
后台线程:如果线程在执行过程中,能够阻止进程的结束,此时就是后台线程