线程
线程和进程
进程:
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线程的执行过程(生命周期)

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,前两种面向字节,而后两种面向字符。