多线程学习笔记

Java并发基础,多线程

本文为自记读书笔记,摘抄自这里

1. 什么是线程和进程

进程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。系统运行一个程序即是一个进程从创建、运行到消亡的过程。在Java中,当我们启动main函数时其实就是启动了一个JVM的进程。而main函数所在的线程就是这个进程中的一个线程,也称主线程。

线程

线程与进程相似,是进程的一个实体,是CPU调度和分派的基本单位,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
Java程序天生就是多线程程序,一个Java程序的运行是main线程和多个其他线程同时运行。

2. 线程和进程的关系

图解进程和线程的关系在这里插入图片描述
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的方法区(JDK1.8之后的元空间)资源,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

总结:线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护,而进程正相反。

扩充思考:程序计数器、虚拟机栈和本地方法栈为什么是私有的,堆和方法区为什么是线程共享的。

3. 并发和并行的区别

  • 并发:同一时间段,多个任务都在执行(单位时间内不一定同时执行)。
  • 并行:单位时间内,多个任务同时执行。

4. 为什么要使用多线程

从总体上来说:

  • 从计算机底层来说:线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核CPU时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
  • 从当代互联网发展趋势来说:现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

深入到计算机底层来探讨:

  • 单核时代:在单核时代多线程主要是为了提高CPU和IO设备的综合利用率。举个例子:当只有一个线程的时候会导致CPU计算时,IO设备空闲;进行IO操作时,CPU空闲。我们可以简单地说这两者的利用率目前都是50%左右。但是当有两个线程的时候就不一样了,当一个线程执行CPU计算时,另一个线程可以进行IOD操作,这样两个的利用率就可以在理想情况下达到100%了。
  • 多核时代:多核时代多线程主要是为了提高CPU利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU只有一个CPU核心被利用到,而创建多个线程就可以让多个CPU核心被利用到,这样就提高了CPU的利用率。

5. 使用多线程带来的问题

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。

6. 线程的生命周期和状态

Java线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态。

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,改状态不同于WAITING,它是可以在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java线程状态变迁如下图所示:
在这里插入图片描述
由上图可以看出:

  • 线程创建之后它将处于NEW新建)状态,调用start()方法后开始运行,线程这时候处于READY可运行)状态。
  • 可运行状态的线程获得了CPU时间片(timeslice)后就处于RUNNING运行)状态。
  • 当线程执行wait()方法之后,线程进入WAITING等待)状态。
  • 进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而TIME_WAITING超时等待)状态相当于在等待状态的基础上增加了超时限制,比如通过sleep(long millis)方法或wait(long milliis)方法可以将Java线程置于TIMED WAITING状态。
  • 当超时时间到达之后Java线程将会返回到RUNNABLE状态。
  • 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到BLOCKED阻塞)状态。
  • 线程在执行Runnable的run()方法之后将会进入到TERMINATED终止)状态。

7. 什么是上下文切换

多线程编程中一般线程的个数都大于CPU核心的个数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来说就是:当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换

上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的CPU时间,事实上,可能是操作系统中时间消耗最大的操作。

Linux相比于其他操作系统(包括其他Unix系统)有很多的优点,其中有一项就是:其上下文切换的模式切换的时间消耗非常少。

8. sleep()方法和wait()方法的区别和共同点

  • 两者最主要的区别在于:sleep()方法没有释放锁,而wait()方法释放了锁
  • 两者都可以暂停线程的执行。
  • wait()方法通常被用于线程间交互/通信,sleep()方法通常被用于暂停执行。
  • wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifuAll()方法。sleep()方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。

9. 我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法

new一个Thread,线程进入了新建状态,调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。而直接执行run()方法,会把run方法当成一个main线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结:调用start方法方可启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

你可能感兴趣的:(多线程)