概念性内容
线程和进程区别?
| 进程 | 线程 |
|---|---|
| 进程是程序的一次运行过程,从创建,运行到销毁,是操作系统运行程序的基本单位,像我们运行一个.exe文件就是启动了一个进程,有的能开启多个进程,比如qq登录多个账号。有的仅开启一个进程,比如360安全卫士 | 一个进程内可以包含1到多个线程,可以认为进程是线程的容器。在Java中,进程作为最小的资源分配单位,而线程是最小的调度单位 |
二者不同点:
- 进程的资源都是独立的,并不共享。
- 线程共享进程内的堆和方法区空间(jdk1.8是元空间,直接使用本地物理内存),每个线程有自己独立的程序计数器、虚拟机栈、本地方法栈。 线程之间切换耗费时间也更少,因此线程也被称为轻量级的进程。
- 进程通信,实现较为复杂,因为进程之间不共享资源,需要额外的协议来支持。比如同一台PC进程通信使用IPC(Inter-process communication),远程PC进程通信依靠Http协议。而线程通信较简单,因为可以访问共享变量。
所以可以看出进程资源不共享,有利于管理,不会造成相互影响,但是上下文切换成本较大。而线程相反,因为有共享资源,所以可能会造成相互影响,不利于管理,但上下文切换成本比进程小得多。
为什么程序计数器、虚拟机栈、本地方法栈是线程私有?
先贴一张G哥的图
程序计数器作用:
- 帮助字节码解释器读取下一条字节码。
- 在多线程环境下,程序计数器用于被记录当前执行的位置,当线程被切换回来时能知道上次执行到哪了。
所以程序计数器私有是为了保存程序执行到的位置。
虚拟机栈作用:Java在方法调用时候,会创建对应的栈帧,保存方法内的局部变量表、操作数栈、常量池引用等信息。方法的调用和返回对应着虚拟机栈的入栈和出栈。所以需要为每一个调用者(线程)提供单独的虚拟机栈不让别的调用者访问到。
本地方法栈:本地方法栈跟虚拟机栈作用差不多,只不过调用的是native本地方法。
在HotSpot虚拟机中,已经将两个虚拟机栈合二为一了,
聊聊堆和方法区?
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
多线程
并行和并发的区别
并行是同一时间执行多个线程的任务,能够同时得到执行。parallel 并发也是同一时间执行多个线程任务,但是不一定得到执行,可能需要排队。concurrent
注意:并行需要在多核心CPU中才能有体现,单核CPU体现不出来,会变成串行执行。java8中的stream流配合多核CPU可以提高性能。
为什么要使用多线程?
- 线程更加轻量,切换和调度资源耗费的更少
- 当今互联网的并发量大,需要使用到多线程
- 单核CPU下,能够提供CPU利用率
- 多核CPU下,可以提高任务执行速度(类似steam流)
使用多线程带来的问题
内存泄漏、线程不安全、以及线程死锁(操作系统中是使用银行家算法解决,即哲学家就餐)等问题
线程的生命周期
Java中的生命周期
操作系统中的生命周期
注:上述图片来自《Java并发编程的艺术》
通过对比,我们发现Java中只有6种状态,而操作系统中有7种状态,少的这一种Ready是被Java合并到了Runnable中。至于为什么合并,原因是因为现在大部分都使用的是分时操作系统,利用时间片轮转来决定线程的执行时间,时间一到就得回到调度队列中等待下一次操作系统的调度。通常这一时间非常短,大概为0.01秒。所以Java认为Ready这一状态并没太大意义,所以合并了。
在Java程序执行时,并不是固定在某一声明周期的,而是会随着代码执行的不同而到不同的声明周期:
- 创建了线程还没有start的时候,为New
- 开始了start为Runable
- 获取不到锁为Blocked
- 调用wait或者sleep方法Waitting
- 调用wait(long timeout),sleep(long timeout)时为Timed_Waitting
- 最后在执行完了run方法中的内容后为Terminated
什么是上下文切换?
线程有自己执行条件,当发生如下情况,会从CPU中退出
- CPU时间片到期。
- 发生阻塞类型事件,调用了系统中断,比如IO事件。
- 调用sleep或者wait方法放弃CPU执行时间。
- 线程执行完毕。
上下文切换时会保存当前线程运行的状态,保存现场,以便下次切换回来的时候能够还原现场。而保存和还原都需要耗费一定的资源,比如内存消耗,CPU消耗等等
线程死锁,以及避免死锁
操作系统中死锁四个必要条件:
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何预防死锁? 破坏死锁的产生的必要条件即可:
- 破坏请求与保持条件 :一次性申请所有的资源。
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件
可以使用银行家算法寻找出安全序列。
sleep和wait方法的区别和相同点
相同:
- 都可以让线程暂停
不同:
- wait会释放锁,而sleep不会释放锁
- wait用于线程间的通信,而sleep用于线程的暂停
- wait不会自动苏醒,而sleep可以。(wait(long timeout)可以自动苏醒)。
调用start方法和run方法区别?
我们调用start方法,会创建一个线程并进入就绪状态,放入就绪队列,当分配到了时间片后,就可以自动执行run方法中的内容。而调用run方法并不会有上面的流程,他只会被当成main线程中的一个普通方法调用。
创建线程有几种方式?
- Thread类
- Runnable接口
- Callable接口(具有返回值的任务,可以搭配FutureTask获取返回值)
- 线程池创建
什么是守护线程
守护线程也是线程的一种,但是以一种守护模式进行的,即别的线程运行完了,即使他的代码还没有运行完也会立即终止。