Java基础-线程和进程(一)
一、什么是进程和线程
1)、进程是程序运行资源分配的最小单位
-
进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、磁盘 IO 等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
-
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为
系统进程
和用户进程
。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。
2)、线程是 CPU 调度的最小单位,必须依赖于进程而存在
- 线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
3)、线程无处不在
- 任何一个程序都必须要创建线程,特别是 Java 不管任何程序都必须启动一个main 函数的主线程; Java Web 开发里面的定时任务、定时器、JSP 和 Servlet、异步消息处理机制,远程访问接口RM等,任何一个监听事件, onclick的触发事件等都离不开线程和并发的知识。
4)、线程并发的好处
-
充分利用 CPU 的资源
因为现在市面上没有 CPU 的内核不使用多线程并发机制的,特别是服务器还不止一个 CPU,如果还是使用单线程的技术做思路, 明显就 out 了。因为程序的基本调度单元是线程,并且一个线程也只能在一个 CPU的一个核的一个线程跑,如果你是个 i3 的 CPU 的话,最差也是双核心 4 线程的运算能力:如果是一个线程的程序的话,那是要浪费 3/4 的 CPU 性能:如果设计一个多线程的程序的话,那它就可以同时在多个 CPU 的多个核的多个线程上跑,可以充分地利用 CPU,减少 CPU 的空闲时间,发挥它的运算能力,提高并发量。 -
加快响应用户的时间
比如我们经常用的迅雷下载,都喜欢多开几个线程去下载,谁都不愿意用一个线程去下载,为什么呢?答案很简单,就是多个线程下载快啊。 -
可以使你的代码模块化,异步化,简单化
例如我们实现电商系统,下订单和给用户发送短信、邮件就可以进行拆分,将给用户发送短信、邮件这两个步骤独立为单独的模块,并交给其他线程去执行。这样既增加了异步的操作,提升了系统性能,又使程序模块化,清晰化和简单化。
二、 CPU核心数和线程数的关系
多核心:
也指单芯片多处理器( Chip Multiprocessors,简称 CMP),CMP 是由美国斯坦福大学提出的,其思想是将大规模并行处理器中的 SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程。这种依靠多个 CPU 同时并行地运行程序是实现超高速计算的一个重要方向,称为并行处理。
多线程:
Simultaneous Multithreading.简称 SMT.让同一个处理器上的多个线程同步执行并共享处理器的执行资源。
- 核心数、线程数:目前主流 CPU 都是多核的。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是 1:1 对应关系,也就是说四核 CPU 一般拥有四个线程。但 Intel 引入
超线程技术
后,使核心数与线程数形成 1:2 的关系。
创建最大线程数的限制(OS):Linux 最大 1000个,Window最大2000个
三、Thread、Runable
线程状态图1)、两种启动方式
启动线程的方式有:
- 1、X extends Thread;,然后 X.start
- 2、X implements Runnable;然后交给 Thread 运行
2)、Thread 和 Runnable 的区别
- Thread 才是 Java 里对线程的唯一抽象;
- Runnable 只是对任务(业务逻辑)的抽象;
- Thread 可以接受任意一个 Runnable 的实例并执行。
start 只能调用一次,并创建一个子线程,run可以被多次调用。run通俗的说就是Thread里面的成员方法,与线程的启动没有关系。只有start才能启动一个线程。
object ThreadUtil {
class MyThread : Thread(){
override fun run() {
super.run()
println(Thread.currentThread().name)
}
}
@JvmStatic
fun main(argc : Array<String>){
val myThread = MyThread()
myThread.name = "My Thread"
myThread.run() //调用的是 main 线程
myThread.start() //只能调运一次, 否则报 IllegalThreadStateException异常
Thread.sleep(1000)
myThread.run() //调用的是 main 线程
}
}
3)、线程中断
安全的中止则是其他线程通过调用某个线程 A 的 interrupt()
方法对其进行中断操作, 中断好比其他线程对该线程打了个呼,“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。因为 java 里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为 true 来进行响应。
程通过方法isInterrupted()
来进行判断是否被中断,也可以调用静态方法Thread.interrupted()
来进行判断当前线程是否被中断,不过 Thread.interrupted()
会同时将中断标识位改写为 false
。
如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在这些阻塞方法调用处抛出InterruptedException
异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为 false。
注意:处于死锁状态的线程无法被中断
四、线程上下问切换
线程上下文是指某一时间点 CPU 寄存器
和程序计数器
的内容,CPU通过时间片分配算法来循环执行任务(线程),因为时间片非常短,所以CPU通过不停地切换线程执行。
上下文切换
线程切换
,同一进程中的两个线程之间的切换进程切换
,两个进程之间的切换模式切换
,在给定线程中,用户模式和内核模式的切换地址空间切换
,将虚拟内存切换到物理内存
CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一任务的状态并执行。任务的状态保存及再加载, 这段过程就叫做上下文切换。
- 每个线程都有一个
程序计数器
(记录要执行的下一条指令
),一组寄存器
(保存当前线程的工作变量
),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。- 寄存器 是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
- 程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。
1、挂起当前任务(线程/进程),将这个任务在 CPU 中的状态(上下文)存储于内存中的某处
2、恢复一个任务(线程/进程),在内存中检索下一个任务的上下文并将其在 CPU 的寄存器中恢复
3、跳转到程序计数器所指向的位置(即跳转到任务被中断时的代码行),以恢复该进程在程序中
上下文切换会导致额外的开销,常常表现为高并发执行时速度会慢串行,因此减少上下文切换次数便可以提高多线程程序的运行效率。
直接消耗
:指的是CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉间接消耗
:指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小
五、死锁
是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
1)、死锁的发生必须具备以下四个必要条件。
- 1、
互斥条件
:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。 - 2、
请求和保持条件
:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。 - 3、
不剥夺条件
:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。 - 4、
环路等待条件
:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
2)、危害
- 1、线程不工作了,但是整个程序还是活着的
- 2、没有任何的异常信息可以供我们检查。
- 3、一旦程序发生了发生了死锁,是没有任何的办法恢复的,只能重启程序,对正式已发布程序来说,这是个很严重的问题。
3)、解决
关键是保证拿锁的顺序一致
两种解决方式
- 1、内部通过顺序比较,确定拿锁的顺序;
public class NormalDeadLock {
private static Object No13 = new Object();//第一个锁
private static Object No14 = new Object();//第二个锁
//第一个拿锁的方法
private static void lanceDo() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (No14){
System.out.println(threadName+" get nO14");
Thread.sleep(100);
synchronized (No13){
System.out.println(threadName+" get nO13");
}
}
}
//第二个拿锁的方法
private static void avDo() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (No13){
System.out.println(threadName+" get nO13");
Thread.sleep(100);
synchronized (No14){
System.out.println(threadName+" get nO14");
}
}
}
//子线程
private static class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try {
lanceDo();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//主线程
Thread.currentThread().setName("MainThread");
MyThread myThread = new MyThread("myThread");
avDo();
myThread.start();
}
}
- 2、采用尝试拿锁的机制(tryLock)。
public class TryLock {
private static Lock No13 = new ReentrantLock();//第一个锁
private static Lock No14 = new ReentrantLock();//第二个锁
//先尝试拿No13 锁,再尝试拿No14锁,No14锁没拿到,连同No13 锁一起释放掉
private static void fisrtToSecond() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(No13.tryLock()){
System.out.println(threadName +" get 13");
try{
if(No14.tryLock()){
try{
System.out.println(threadName +" get 14");
System.out.println("fisrtToSecond do work------------");
break;
}finally{
No14.unlock();
}
}
}finally {
No13.unlock();
}
}
Thread.sleep(r.nextInt(3));
}
}
//先尝试拿No14锁,再尝试拿No13锁,No13锁没拿到,连同No14锁一起释放掉
private static void SecondToFisrt() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(No14.tryLock()){
System.out.println(threadName +" get 14");
try{
if(No13.tryLock()){
try{
System.out.println(threadName +" get 13");
System.out.println("SecondToFisrt do work------------");
break;
}finally{
No13.unlock();
}
}
}finally {
No14.unlock();
}
}
Thread.sleep(r.nextInt(3));
}
}
private static class TestThread extends Thread{
private String name;
public TestThread(String name) {
this.name = name;
}
public void run(){
Thread.currentThread().setName(name);
try {
SecondToFisrt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try {
fisrtToSecond();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
六、活锁
两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。
解决办法:每个线程休眠随机数,错开拿锁的时间。