- 线程基础
- 引言
- 进程与线程
- 线程与进程的比较
- 线程的实现
- 内核线程(KLT)
- 用户线程(ULT)
- 混合线程
- Java线程的实现
- 线程的调度
- 协同式线程调度
- 抢占式线程调度
- Java线程的调度
- 线程优先级
- JAVA线程的生命周期
- 状态转换
- 线程基础机制
- 基本使用
- 创建任务
- 实现Runnable接口
- 实现Callable接口
- 继承Thread类
- 执行任务
- 使用Exector
- 线程休眠
- 线程优先级
- 线程让步
- 守护线程- daemon
- 加入线程
- 异常
- 中断
- InterruptedException
- 线程之间的协作
- 等待/通知经典范式
- wait() notify() notifyAll()
- await() signal() signalAll()
- ThreadLocal
进程是可并发执行的程序在某个数据集合上的一次计算活动,也是操作系统进行资源分配和调度的基本单位。
进程是操作系统中除处理器外进行的资源分配和保护的基本单位,它有一个独立的虚拟地址空间,用来容纳进程映像(如与进程关联的程序与数据),并以进程为单位对各种资源实施保护,如受保护地访问处理器、文件、外部设备及其他进程(进程间通信)。
进程的两项功能
1. 进程是资源分配和保护基本单位。
2.进程同时又是一个可独立调度和分派的基本单位。
进程作为一个资源拥有者,在创建、撤消、切换中,系统必须为之付出较大时空开销。所以系统中进程的数量不宜过多,进程切换的频率不宜过高,但这也就限制了并发程度的进一步提高。
为解决此问题,人们想到将进程的上述两个功能分开,即对作为调度和分派的基本单位,不同时作为独立分配资源的单位;对拥有资源的单位,不对之进行频繁切换,线程因而产生。
线程是操作系统进程中能够并发执行的实体,是处理器调度和分派的基本单位。
每个进程内可包含多个可并发执行的线程。线程自己基本不拥有系统资源,只拥有少量必不可少的资源:程序计数器、一组寄存器、栈。同属一个进程的线程共享进程所拥有的主存空间和资源。
目前线程是Java里面进行处理器资源调度的最基本单位。
线程与进程的比较-
调度
在传统OS中,拥有资源、独立调度和分派的基本单位都是进程,在引入线程的系统中,线程是调度和分派的基本单位,而进程是拥有资源的基本单位。
在同一个进程内线程切换不会产生进程切换,由一个进程内的线程切换到另一个进程内的线程时,将会引起进程切换。
-
并发性
在引入线程的系统中,进程之间可并发,同一进程内的各线程之间也能并发执行。因而系统具有更好的并发性。
-
拥有资源
无论是传统OS,还是引入线程的OS,进程都是拥有资源的独立单位,线程一般不拥有系统资源,但它可以访问隶属进程的资源。即一个进程的所有资源可供进程内的所有线程共享。
-
系统开销
进程的创建和撤消的开销要远大于线程创建和撤消的开销,进程切换时,当前进程的CPU环境要保存,新进程的CPU环境要设置,线程切换时只须保存和设置少量寄存器,并不涉及存储管理方面的操作,可见,进程切换的开销远大于线程切换的开销。
同时,同一进程内的各线程由于它们拥有相同的地址空间,它们之间的同步和通信的实现也变得比较容易。
线程的实现线程的实现主要有三种方式
- **用户级线程(User Level Thread,ULT):(1:N)**对于这种线程的创建、撤消、和切换,由用户程序来实现,内核并不知道用户级线程的存在。
- **内核级线程(Kernel Level Thread ,KLT):(1:1)**它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。
- **混合式线程:(N:M)**同时支持ULT和KLT两种线程。
P:进程
LWP:内核线程的一种高级接口-轻量级进程,也可以理解为线程,每个轻量级进程都由一个内核线程支持。因此只有先支持内核线程才能有轻量级线程。
内核线程就是直接操作内核的线程。由线程调度器来调度这些线程,并将线程的任务映射到各个CPU。
轻量级进程与内核线程的关系是1:1,也成为一对一线程模型。轻量级进程与内核线程的关系如上图所示。
优点:
对多处理器,内核可以同时调度同一进程的多个线程
阻塞是在线程一级完成
缺点:
在同一进程内的线程切换调用内核,系统开销较大
用户线程(ULT)P:进程
UT:狭义上的用户线程,完全建立在用户空间的线程库上,内部不能感知到用户线程的存在和实现。用户线程的创建、同步、销毁和调度全部在用户态下完成,不需要内核的帮助。
使用用户线程实现的方式称为1:N实现,也成为1:N线程模型。进程与用户线程的关系如上图所示。
优点:
线程切换不调用内核,操作快低消耗
调度是应用程序特定的:可以按需要选择好的算法
ULT可运行在任何操作系统上(只需要线程库),可以在一个不支持线程的OS上实现
缺点:
由于大多数系统调用是阻塞的,因此一个用户级线程的阻塞会引起整个进程的阻塞
内核只将处理器分配给进程,同一进程中的两个线程不能同时运行于两个处理器上等问题复杂,且解决难度高
混合线程既存在用户级线程,又内核级线程。
用户线程还是完全建立在用户空间中,因此用户线程的创建,切换,析构操作依然不经过内核,是快速度低消耗的;而操作系统支持的轻量级进程则成为了用户线程和内核线程之间的桥梁,这样就可以使用内核的线程调度功能和处理器映射了。在这中实现中,用户线程与轻量级进程的数量比是不确定的,是N:M关系,如上图所示。这是一种多对多线程模型。
Java线程的实现主流平台的主流的Java虚拟机采用线程模型是1:1的线程模型,基于操作系统的原生线程模型来实现。如HotSpot。
线程的调度线程调度指的是系统为线程分配处理器使用权的过程,主要分为协同式线程调度和抢占式线程调度。
协同式线程调度线程的执行时间由线程本身控制,线程把自己的作业完成之后,主动通知系统切换到另一个线程上。这种调度方式通常用于批处理系统中。
优点
- 实现简单
- 切换线程操作对线程可知,一般没有同步问题
缺点
- 线程执行时间不可控,容易阻塞
每个线程将由系统来分配执行时间,线程切换也不由线程本身决定。这种调度方式通常用于分时系统和实时系统中。
优点
- 线程的执行时间是系统可控的,不会因为一个线程阻塞导致整个进程乃至系统的阻塞问题。
缺点
- 线程切换不可控,存在同步问题
Java使用线程调度方式是抢占式的。
线程优先级可以通过线程优先级来影响线程调度,是影响并非绝对控制。
不同系统、不同语言之间的线程优先级存在差异。Java一共提供了10个级别线程优先级,window线程优先级一共提供了7个级别线程优先级(THREAD_PRIORITY_IDLE和下表的6种)。
Java与window线程优先级对比图
Java线程优先级window线程优先级1 (Thread.MIN_PRIORITY)THREAD_PRIORITY_LOWEST2THREAD_PRIORITY_LOWEST3THREAD_PRIORITY_BELOW_NORMAL4THREAD_PRIORITY_BELOW_NORMAL5 (Thread.NORM_PRIORITY)THREAD_PRIORITY_NORMAL6THREAD_PRIORITY_ABOVE_NORMAL7THREAD_PRIORITY_ABOVE_NORMAL8THREAD_PRIORITY_HIGHEST9THREAD_PRIORITY_HIGHEST10 (Thread.MAX_PRIORITY)THREAD_PRIORITY_CRITICALJava一共提供了10个级别线程优先级。
JAVA线程的生命周期java语言一共为线程定义了6中状态,这6种状态构成了java线程的生命周期。一个线程只能有且只有其中的一种状态,并可以通过特定方法在相关状态中进行转换。
-
新建(New):线程创建后尚未启动的状态
-
可运行(Runnable):线程可能正在执行,也可能在等待CPU执行时间,包括了操作系统线程状态中的Runing和Ready。
-
无限期等待(Waiting):不会被分配处理器执行时间,需要被其他线程显示唤醒,以下方法会让线程无限期等待
-
没有配置Timeout参数的Object::wait()方法 (退出方法 Object.notify() / Object.notifyAll())
-
没有配置Timeout参数的Thread::join()方法 (退出方法 被调用的线程执行完毕)
-
LockSupport::park()方法
-
-
限期等待(Timed Waiting):不会被分配处理器执行时间,无需其他线程唤醒,在一定时间之后会被系统自动唤醒,以下方法会让线程限期等待
- 配置Timeout参数的Object::wait()方法 (退出方法 时间结束 / Object.notify() / Object.notifyAll())
- 配置Timeout参数的Thread::join()方法 (退出方法 时间结束 / 被调用的线程执行完毕)
- LockSupport::parkNanos()方法
- LockSupport::parkUntil()方法
- Thread::sleep()方法 (退出方法 时间结束)
-
阻塞(Blocked):线程等待获取一个排他锁,当另一个线程放弃这个锁的时候,会结束这个状态。
阻塞的状态分三种。
-
等待阻塞
正运行的线程调用o.wait方法时,jvm会将线程移去等待队列,此时线程变为阻塞状态。
-
同步阻塞
正运行的线程尝试获取其他线程占用的对象同步锁的时候,jvm会将线程放入**锁池(Lock Pool)**此时线程状态变为阻塞。
-
其他阻塞
-
当线程执行sleep,join,I/O请求时,jvm把线程转为阻塞,直到sleep结束。join和I/O完毕或者超时。
-
-
结束(Terminated):线程以及结束执行
-
正常结束
-
Error或者未捕获的Exception
-
手动结束 调用stop,抛出异常ThreadDeathError,释放线程所持有的锁和资源。这种会造成锁的混乱或者死锁 不建议使用
-
来自thinking in java 4th edition
基本使用 创建任务一个完整的任务可以拆分成多个子任务,在不同的线程中执行,来增加任务执行的并行度 ,从而提高执行效率。
实现Runnable接口在run()方法中编写执行代码。
public class MyJob implements Runnable {
@Override
public void run() {
System.out.println("I`m MyJob");
}
}
实现Callable接口
可以从任务中返回结果值
public class MyCallable implements Callable {
@Override
public String call() throws Exception {
return "I`m MyCallable";
}
}
继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("I`m MyThread");
}
}
执行任务
通过显示的调用start()来执行。
public class Client {
public static void main(String[] args) throws Exception {
MyJob instance = new MyJob();
// 实际开发中 使用线程池管理线程
Thread threadRunnable = new Thread(instance);
threadRunnable.start();
MyCallable mc = new MyCallable();
FutureTask ft = new FutureTask(mc);
Thread threadCallable = new Thread(ft);
threadCallable.start();
System.out.println(ft.get());
MyThread mt = new MyThread();
mt.start();
}
}
I`m MyJob
I`m MyCallable
I`m MyThread
使用Exector
从JDK5开始,新增了Executor框架,用来独立执行机制。
public static void main(String[] args) throws Exception {
MyJob instance = new MyJob();
// 线程池的创建最好是手动的,具体可以参考阿里的java开发规约
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?