北川广海の梦

北川广海の梦

多线程基础

2020-05-24

什么是线程和进程? 线程与进程的关系,区别及优缺点?

进程是操作系统中运行的一段程序的实例,它拥有自己独立的内存空间。不同的进程,是不能直接访问其他线程的内存的,只有通过特殊方式进行进程间通讯。

而线程是进程中的执行单元,线程负责执行程序中的逻辑。所有的程序中,都至少有一个线程来进行执行。在同一个进程下的不同线程,它们可以很方便的共享内存,以Java来说,除了线程私有的程序计数器,本地方法栈,虚拟机栈之外的内存,线程都是可以共享的。

线程和进程的关系,就像火车和火车车厢,一个火车可以有多节车厢。但车厢不能脱离火车单独存在,没有完整的火车,也就是线程不能独立存在,必须存在于进程之内。

它们的区别在于,进程的创建开销大得多,并且在操作系统中的一个进程挂掉之后,不会影响其他的进程。而进程中的某一个线程挂掉,就会导致整个进程挂掉。而且线程的上下文切换开销比进程小得多。所以线程又被称为轻量级进程。

并发与并行的区别

个人理解:并发是指宏观上,在某一段时间内,多个任务都在执行。而并行,是指确定的某一时刻,多个任务同时在进行。并行需要多处理器支持,例如两个CPU核心,就可能支持同一时刻两个任务执行。而并发很可能是一个CPU核心,快速执行任务,让人感觉,像是感觉像是同时执行了两个任务。

为什么要使用多线程

为了充分利用CPU资源,让我们的任务在某些情况不被其他任务干扰。将占用大量时间的任务,让后台线程去执行,让用户的交互界面不是无法响应状态。可能带来性能上的提升,例如对于多核CPU,可以切实的让两个任务同时执行,以提高效率。

多线程可能带来什么问题

许多情况下,线程是远远大于CPU核心数量的,所以只能通过上下文切换的方式来交替运行程序。多线程需要更多的内存空间。多线程可能带来资源的线程安全问题,可能导致死锁等。

线程的生命周期和状态

线程刚被创建出来的时候,处于New状态,调用线程的start()方法,则会使线程进入Runnable状态,处于此线程的状态,在得到CPU分配的时间片之后,就会开始执行,处于正在执行的线程就,状态为Running。线程如果执行了wait()方法,则会进入到Waiting状态,直到得到其他线程的通知,也就是由其他线程来对这个线程进行唤醒才会重新进入Runnable状态。而Timed_Waiting则是让这线程等待一段时间,超过时间之后会自动恢复。如果线程调用了sleep(long)方法,那么在一定时间内线程将处于Blocked状态,或者线程调用了一个阻塞IO方法,在方法返回前,线程也将处于阻塞状态。当线程试图获得一个锁,而锁被其他线程占用的时候,线程也将进入阻塞状态。当线程的Run()方法被全部执行完毕,或者执行过程中出现未捕获的异常或错误,线程就将处于终止状态。

什么是上下文切换

上下文切换指的是对于多个需要同时并发执行的任务,为了让不同的线程都能得到有效执行,操作系统采用为每个不同的线程分配时间片的方式,轮流执行这些任务。每一次时间片重新分配的过程,就叫一次上下文切换。在切换的时候,CPU会先将自己当前的执行状态,寄存器内容写入到内存中,然后从内存中读取下一个任务的信息,并开始任务的执行。上下文切换是有消耗的,主要就在于将保存CPU当前信息这一过程。

什么是线程死锁?如何避免死锁

死锁是多个线程争夺资源,而互相等待,导致无限期被阻塞的情况。例如某一个任务需要资源A,B 线程1和线程2都要执行这个任务,但是线程1拿到了资源A,线程2拿到了资源B,为了能顺利执行任务,线程1会阻塞,直到资源B被释放,线程2会阻塞,直到资源A被释放,此时,它们都在等待对方释放资源,于是双方都会无限期的被阻塞。

可以通过破坏产生死锁的必要条件来避免,可以让资源一次性的进行分配。如果线程已经占有了部分资源,想进一步拿到其他资源时候,如果无法得到,那么主动释放当前占有的资源。资源的分配,都按照同一的顺序进行申请,而释放按照相反的顺序。

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

它们最大的区别在于sleep()方法不会释放锁,而wait()会将锁释放。它们都可以使线程暂停。wait常用于线程间的通信,因为wait状态的线程,必须要通过其他的线程的唤醒,才能重新就绪,sleep则常用于让线程暂停执行。

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

调用start方法的作用是让线程处于就绪状态,当得到了时间片之后就会开始执行run()中的方法,而直接调用run()方法和普通的方法调用没有区别,是在当前线程执行run()方法里面的逻辑。