多线程相关知识的整理(一)

一、为什么要使用多线程

1. 避免cpu空转:一般cpu和cash内存的处理数据的速度不一样,以http server为例,但单线程响应请求了请求,但是cpu已经处理完成了,其他的,数据访问,磁盘io操作等还没有完成,这样就造成了cpu处于空闲状态,这样就造成大量的性能浪费。
2. 避免阻塞:如果是一个线程的调用,如果其中的某个环节出现了问题,那么那么这个线程必定会影响到其他线程的工作,这样有点类似异步调用。
3. 提升性能:提升性能在满足一定的条件,采用多线程操作的工作模式,可以提供运行效率
一定的条件:
(1)该任务具有并发性,允许并发执行,即可以拆分成很多个小任务,并且这些任务并没有什么先后顺序,且小任务之间不能存在资源竞争,如:一个任务将某个资源锁住了,另一个资源则得等待这个任务的结束,这两个任务则不具备并发性
(2)只有在cpu存在瓶颈的时候,因为cpu中同时多个线程并发执行肯定能提高工作效率。但是如果是io等影响了处理数据的时间,那么无论将任务分解成几个小任务进行,都无法提高运行的效率
(3)需要多核cpu采用多线程,如果只是在一个cpu中不断的进行线程的切换,反而会增加额外的开销,从而降低了性能,类似两个锅却只有一个炉需要不断的变换菜色进行烹饪。
注:多核cpu的速度的提升比例:1/[(1-P)+(P/N)],其中P是分成的可并行的任务比例,N是cpu数量

二、线程和进程

1.进程和线程
进程:进程就是操作系统中能够独立执行,并且作为资源分配最基本(最小)单位。而我们常说的操作系统的运行,就是一个进程的创建,运行直至消亡的过程。
线程:是操作系统能够进行运算调度的最小单位,即存在于进程中,一个进程至少包括一个及一个以上线程在这里插入图片描述

注:
(1)同个类中的多个线程共享进程中的方法区和堆的资源,但是每个线程都有自己的程序计数器,本地方法栈和虚拟机栈
(2)线程的共享区域随着虚拟机的启动而启动,随着虚拟机的关闭而销毁
(3)直接内存是堆外内存,是受本机内存的大小的限制,而不受java堆大小的限制

1.虚拟机栈(栈内存)
定义:用来描述java的内存模型。
功能:它是当前栈中存储这局部变量表,操作数栈,动态链接,方法出口等信息。同时,栈帧存储着运行时的数据及其数据结构,处理动态链表的返回值和异常分派
执行过程如下:

在这里插入图片描述

当一个方法被执行的时候,虚拟机会相应的生成一个栈帧,方法的执行与返回相应着栈帧在虚拟机中的出栈和入栈。无论是方法的正常运行结束还是抛出异常,都视为方法的结束
虚拟机报错:
(1) StackOverflowError:
当请求的单个线程的大小大于虚拟机的内存大小
(2) OutOfMemoryError:
当虚拟机的内存不够用时,如果有某个线程请求虚拟机内存大小,则会报错
2.本地方法栈:

功能与虚拟机栈类似,存在着线程隔离的特点,也有StackOverflowErrorOutOfMemoryError的异常,但是这个位置执行的是JVM的native方法,而虚拟机栈中的执行的是JVM的java方法。如何执行native方法,如何服务native方法,这个应该由具体的虚拟机去执行。

3.程序计数器:存储着下一个命令操作执行的地址

作用:
(1)字节码解释器是通过改变程序计数器来依次读取指令,从而控制代码的控制,是当前线程所执行字节码的行号指示器,如:执行顺序,选择,循环,异常处理等
(2)在执行多线程的时候,记录当前线程所处的状态,这样有助于线程切换的时候,能够恢复的正确的位置。
注:程序计数器不存在内存溢出的问题,因为它记住的是下个需要执行的命令的地址,即使是发生死循环也不会让它这部分区域产生内存溢出。

4.堆:在jvm中运行产生的数据和创建的对象都是被存储在堆中,它是线程共享的区域,也是垃圾回收的主要区域。
5.方法区:方法区也被称为永久代,用来存储静态变量,类信息, 即时编译器编译后的机械码,运行时常量池的数据。

在这里插入图片描述

为啥堆和方法区是共享的,而线程的程序计数器,虚拟机栈和本地方法栈确实私有的?

1.首先,因为堆是进程中最大的一个内存,很多新创建的对象都存放于堆中,方法区存放这很多已被加载的常量,类信息,静态变量即编辑器编辑后的信息,将这些资源进行共享,可以有效的提高线程的执行效率。
2.虚拟机栈和本地方法栈私有,是为了调用方法的时候创建栈帧压入虚拟机栈中去,这样栈帧中的局部变量表就不会被访问到。程序计数器是当前字节码的型号指示器,肯定不能公用。
注:
并行:是真正意义上的多个线程同时运行,只存在多核系统中。
并发:其实是多个线程交替运行,(单核)cpu通过时间切片切换的方式

三、 线程的生命周期

线程的5个状态:

1.初始(new)状态:通过实例化刚刚创建
2.就绪(Runnable)状态:线程通过.start()方法开始进入就绪状态,等待cpu分调度执行(就绪状态是进入进行状态的唯一入口)
3.进行(Running)状态:就绪状态的线程获得cpu的调度执行
4.阻塞(Blocked)状态:处于运行状态中的某个线程由于某种原因暂时放弃了对于cpu的使用权力,直到该进入就绪态才可以进去cpu中去。

阻塞一般有三种阻塞情况:

1.同步阻塞:线程获得同步锁synchronized,如果该同步锁被其他的线程占用,那么jvm会将该线程放置在lock pool(锁池)中去
2.等待阻塞:运行的线程获得wait()方法,jvm会将这个线程放置在wait队列中去
3.其他阻塞:运行态中的线程执行sleep()方法,join()方法,或者等待I/O操作,jvm会将这几个线程置入阻塞状态。等待sleep()状态超时,或者join()等待线程超时,或者I/O处理结束,线程会重新进入running状态。**

5.终止(dead)状态:当一个线程执行run(),main()方法结束,或者因为run()方法结束
情况如下图:
在这里插入图片描述

1.一般线程通过new Thread()方法进行创建
2.然后通过.start()方法进行线程从初始态进入运行
3.处于就绪态的线程等待的线程获得cpu时间片进入进行状态
4.正在运行中的线程调用了yield()方法或者cpu时间片时间到了,就自动进入就绪状态
5.正常执行的线程执行了sleep()方法、I/O阻塞、等待同步锁、等待通知、调用了suspend方法后,会挂起线程,然后将线程放进去block队列中去
6.阻塞状态中的线程如果sleep时间结束,I/O操作结束,获得同步锁,收到通知,调用了resume方法,那么该线程会被重新调到就绪列中等待cpu调度,如果获得cpu时间片,则会重新进入运行态。
7.处于进行态中的线程,在调用run()方法或者call()方法执行完成,调用stop()方法停止线程或者线程因为其他的一些原因结束,该线程都处于终止状态

四、线程的方法

1.wait方法

调用wait()方法,线程直接进入waiting状态,只有等到notify(notifyAll)的方法来通知唤醒才能返回。在调用wait()方法后,会将该线程释放对象的锁。所以wait()方法常用于同步方法或者同步代码块中。Wait方法作用的是Object类中的对象方法,作用的是对象本身

2.sleep方法。

调用sleep方法,让当前的线程先进入time-waiting状态,进入阻塞状态,不会释放当前所占有的锁。只有当线程超时了,或者被interrupt方法,然后会抛出异常interruptException,提前唤醒了该线程。Sleep方法是thread中的静态方法,主要用于当前的线程,不考虑线程的优先级。

3.yield方法

让步方法,调用yield方法会让当前线程释放cpu时间片,然后重新进入就绪状态中与其他的线程争夺cpu时间片。不会抛出任何异常。

4.interrupt方法

1. 首先interrupt方法并不会中断一个处于running状态的线程而终止,它改变的仅仅只是线程当中的中断标识位置而已
2. 若是一个调用了sleep方法而进入阻塞态的线程,采用interrupt方法,可以提前终止time-waiting状态,并且抛出interruptException状态,重新唤醒该线程

5.join方法

等待其他线程结束进入阻塞方法,即如果当前线程如果调用了另一个线程的join()方法,那么当前线程就开始进入阻塞状态,只有当另一个线程的执行结束了,这个线程才开始进入就绪状态,与其他的线程进行cpu时间片的争夺。很多情况下,主线程启动了子线程的话,一般需要等待子线程的返回结果收集并处理再退出,这时候,就需要用到join方法。

6.notify(notifyAll)方法

属于Object类中的一个方法,notify是唤醒等待队列中的一个对象,被唤醒的线程则会进入就绪状态与其他的对象进行cpu的争夺

7.setDaemon方法

一般用setDaemon方法定义一个守护线程,也叫做服务线程,是一种特殊的线程,独立于控制中端并且周期性的执行执行某种任务。它是属于jvm级别,(jvm中的所有的线程都是属于守护线程)如垃圾回收线程。服务线程的优先级一般都很低,只有当其他的线程中的非守护结束了,守护线程才会结束,不然守护线程就不会退出。

sleep方法和wait方法的区别:
  • 1.sleep方法属于Thread静态方法,wait方法属于Object中的实例方法
  • 2.sleep方法只是暂停执行的时间,将cpu暂时让给其他的线程,但是其监控状态仍然继续,等到时间结束后,又回复运行状态,且处于睡眠状态的线程不会释放对象锁
  • 3.调用wait方法的线程会放弃对象锁,并且进入等待该对象的等待锁池中去,只有当此对象被notify(notifyAll)方法才能重新获得对象锁,并且进入运行状态。
start方法和run方法的区别
  • 1.start方法用于启动线程,实现真正意义上的多线程运行。调用start方法后,无需经过run方法体的代码执行完毕,就可以继续执行下面的代码
  • 2.当通过Thread类的方法启动一个线程,那么这个线程是处于就绪状态而不是进行状态。
  • 3.run方法也叫做线程体,包含了要执行的逻辑代码。当一个线程调用run方法的时候,那么直接进入进行状态,开始运行run中的方法。在run中的方法运行结束的时候,则该线程终止,cpu再次调用其他的线程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值