进程:
1.当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程
2.进程可以理解为一个程序的实例,可以又多个(比如记事本,浏览器多开)也可以只有一个(比如360,电脑管家)
3. *** 作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是 资源分配的最小单位。
线程:
1.线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程
2.一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
3.线程,有时被称为轻量级进程(Lightweight Process,LWP),是 *** 作系统调度(CPU调度)执行的最小单位。
区别:
进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
进程拥有共享的资源,如内存空间等,供其内部的线程共享
进程间通信较为复杂同一台计算机的进程通信称为 IPC(Inter-process communication)
不同计算机之间的进程通信,需要通过网络,并遵守共同的协议, 例如 HTTP,ftp等等
线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
我们可以在window上查看任务管理器来观察进程与线程
以上内容都是属于一个个程序,也就是进程,比如我们打开idea,里面的每一条就是属于idea进程里面的线程,它们对应执行自己的功能 ,cpu划分资源是直接划给idea再由idea分配给具体线程
上下文切换
上下文切换是指CPU从一个进程或线程到另一个进程或线程的切换。
为啥要切换?线程切换和进程切换有啥区别?
上下文切换是多任务 *** 作系统的一个基本特性。 在多任务 *** 作系统中,多个进程似乎同时在一个CPU上执行,彼此之间互不干扰。这种并发的错觉是通过快速连续发生的上下文切换(每秒数十次或数百次)来实现的。这些上下文切换发生的原因是进程自愿放弃它们在CPU中的时间,或者是调度器在进程耗尽其CPU时间片时进行切换的结果。 上下文切换通常是计算密集型的。就CPU时间而言,上下文切换对系统来说是一个巨大的成本,实际上,它可能是 *** 作系统上成本最高的 *** 作。因此, *** 作系统设计中的一个主要焦点是 尽可能地避免不必要的上下文切换 。与其他 *** 作系统(包括一些其他类unix系统)相比,Linux的众多优势 之一是它的上下文切换和模式切换成本极低。 上下文切换只能在内核模式下发生。线程生命周期
对于 *** 作系统而言,它的线程是有五个状态:
初始状态、可运行状态、运行状态、休眠状态和终止状态。
1. 初始状态 , 指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在 *** 作系统层面,真正的线程还没有创建。 2. 可运行状态 ,指的是线程可以分配 CPU 执行。在这种状态下,真正的 *** 作系统线程已经被成功创建了,所以可以分配 CPU 执行。 3. 当有空闲的 CPU 时, *** 作系统会将其分配给一个处于可运行状态的线程,被分配到CPU 的线程的状态就转换成了 运行状态 。 4. 运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到 休眠状态 ,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。 5. 线程执行完或者出现异常就会进入 终止状态 ,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。 这五种状态在不同编程语言里会有简化合并。 Java 语言里则把可运行状态和运行状态合并了,这两个状态在 *** 作系统调度层面有用,而 JVM 层面不关心这两个状态,因为 JVM 把线程调度交给 *** 作系统处理 所以从 *** 作系统层面看java里面的线程应该是四种状态: 初始 ,运行,休眠,终止 实际Java 语言中线程共有六种状态,分别是: 1. NEW(初始化状态) 2. RUNNABLE(可运行状态+运行状态) 3. BLOCKED(阻塞状态) 4. WAITING(无时限等待) 5. TIMED_WAITING(有时限等待) 6. TERMINATED(终止状态) 在 *** 作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态, 即休眠状态。也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权。JAVA线程实现
这个我相信大部分人都是比较清楚的,常用的就那三种
new Thread 实现Runnable 实现 Callable
还有同学可能会想到什么线程池之类的,其实本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程,调用Thread#start启动线程最终都会调用Thread#run方法
线程创建完成,要运行是如何运行呢?
下面是java源码的执行流程:
Java线程属于内核级线程
JDK1.2—— 基于 *** 作系统原生线程模型来实现。Sun JDK,它的Windows版本和Linux版本 都使用一对一的线程模型实现,一条Java线程就映射到一条轻量级进程之中。 内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。 用户级线程(User Level Thread,ULT): *** 作系统内核不知道应用线程的存在。 协程用户级线程也叫协程,是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被 *** 作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),具有对内核来说不可见的特 性。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
优点:
线程的切换由 *** 作系统调度,协程由用户自己进行调度,因此减少了上下文切换,提 高了效率。 线程的默认stack大小是1M,而协程更轻量,接近1k。因此可以在相同的内存中开启 更多的协程。 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控 制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。 注意: 协程适用于被阻塞的,且需要大量并发的场景(网络io)。不适合大量计算的场景。总结一下:
java是内核级线程,使用的是 *** 作系统直接给出来的,比较占用资源,而且本质上只有
new Thread一种线程使用方式,其他几种只是方便使用的扩展
Thread常用方法sleep()
1 调用 sleep 会让当前线程从 Running 进入TIMED_WAITING状态,不会释放对象锁
2 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException,并且会清除中断标志
3 睡眠结束后的线程未必会立刻得到执行
4 sleep当传入参数为0时,和yield相同
yield()
1 yield会释放CPU资源,让当前线程从 Running 进入 Runnable状态,让优先级更高
(至少是相同)的线程获得执行机会, 不会释放对象锁 ; 2 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比 它优先级更高的线程; 3 具体的实现依赖于 *** 作系统的任务调度器
join()
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。
例:在A线程里调B线程的join方法时,要先B线程执行完成,然后才会继续执行A
stop()
终止线程,stop会释放对象锁,可能会造成数据不一致。
一般不用stop
interrupt(): 将线程的中断标志位设置为true,不会停止线程 isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位 Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为 fasle 用这三个方法结合判断结束线程会更好 线程之间通信volatile
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线 程之间进行通信。
等待唤醒(等待通知)机制
等待唤醒机制基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,
线程将进入等待队列进行等待直到被唤醒。 LockSupport LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用 unpark则为指定线程提供“许可”。 使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒 *** 作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。管道输入输出流
它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现: PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)