并发编程线程基础
一、什么是线程
线程是进程的一个实体,线程本身不会单独存在。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。
多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。
二、线程的创建与运行
- 继承Thread类,重写run方法
- 实现Runnable接口中的run方法
- 实现Callable中的call方法(含返回值,通过FutureTask实现)
使用继承的优缺点
- 优点:在run方法中直接使用this可以获取当前线程。
- 缺点:Java不支持多继承;任务和代码没有分离,当多个线程执行一样的任务的时候,需要多份任务代码
三、线程等待与通知
- wait():调用线程被阻塞挂起,直到发生notify()或者notifyAll()或者调用了该线程的interrupt()方法抛出InterruptedException异常才会被返回。如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait方法的线程会抛出IllegalMonitorStateException异常。
- wait(long timeout):如果一个线程调用共享对象的该方法挂起后,没有在指定的timeout ms时间内被其他线程调用该共享变量的notify()或者notifyAll()方法唤醒,那么该函数还是会因为超时而返回。
- wait(long timeout, int nanos):内部调用的是wait(long timeout),只有的当nanos>0时才会让timeout递增1。
- notify():随机唤醒一个在该变量上调用wait系列方法后被挂起的线程,被唤醒后不能马上从wait方法返回并继续执行,必须获取了共享对象的监视器锁后才可以返回。
- notifyAll():唤醒所有在该变量上调用的wait系列方法而被挂起的线程。
四、等待线程执行终止的join方法
join是Thread类直接提供的,join是无参且返回值为的void方法。假设线程A调用了线程B的join方法后被阻塞,当其他线程调用了线程A的interrupt方法中断了线程A时,线程A会抛出InterruptedException。
五、让线程睡眠的sleep方法
Thread类中有一个静态的sleep方法,当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是这期间不参与cpu的调度,但是该线程所拥有的监视器资源还是持有不让出的。如果睡眠期间,其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回。
六、让出CPU执行权的yield方法
Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际就是暗示线程调度器当前线程请求让出自己的cpu使用,但是线程调度器可以无条件忽视这个暗示。
sleep和yield方法的区别在于,当线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程下一轮调度时就有可能调度到当前线程执行。
七、线程中断
Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
- void interrupt()方法:中断实现,设置中断标志为true并立即返回。
- boolean isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。
- boolean interrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false,该方法如果发现当前线程被终端,则会清除中断标志,并且该方法时static,可以通关Thread类直接调用。
八、理解线程上下文切换
上下文:线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。
线程上下文切换的时机
- 当前线程的CPU时间片使用完处于就绪状态
- 当前线程被其他线程中断时
九、线程死锁
什么是线程死锁?
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续下去。
死锁的条件
- 互斥条件:指线程对已获取到的资源进行排它性使用,即该资源同时只由一个线程占用。
- 请求并持有条件:指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新资源已被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。
- 不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源。
- 环路等待条件:指在发生死锁时,必然存在一个线程-资源的环形链。
如何避免线程死锁
资源的有序性破坏了资源的请求并持有和环路等待条件,因此避免了死锁。
十、守护线程与用户线程
Java线程分两类:守护线程和用户线程。
它们之间的区别在于,守护线程是否结束并不影响JVM的退出,但是只要有一个用户线程还没结束,正常情况下JVM就不会退出。
Tomcat的NIO实现NioEndpoint中的一组接收线程和处理线程属于守护线程。
十一、ThreadLocal
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都有这个变量的一个本地副本。
ThreadLocal实现原理
在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型是HashMap,其中key为我们定义的ThreadLocal变量的this的引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。
ThreadLocal不支持继承性
同一个ThreadLocal变量在父线程中被设置值后,在子线程是获取不到的。
InheritableThreadLocal类
特性就是让子线程可以访问父线程中设置的本地变量。
实现原理:InheritableThreadLocal类通过重写代码,让本地变量保存到了具体线程的inheritableThreadLcoals变量里面,那么线程在通过InheritableThreadLcoal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造方法会把父线程中的inheritableThreadLocals变量里面的本地变量父子一份保存到子线程的inheritableThreadLcoals变量里面。
本文详细介绍了线程的概念、创建与运行方式、线程间同步机制如等待与通知等,并探讨了线程上下文切换、死锁避免策略及守护线程的作用。此外还深入解析了ThreadLocal的工作原理。
5647

被折叠的 条评论
为什么被折叠?



