线程专题 -- 线程的创建,状态,工作过程,常见方法
线程(Thread)是并发编程的基础,一般会作为并发编程的起始问题,用于引出更多的关于并发编程的面试问题。对于线程的掌握程度也决定了你对并发编程的掌握程度。
什么是进程?线程?区别?
详细介绍请查阅我的另一篇博文——进程?线程?协程?
线程安全?
详细介绍请查阅我的另一篇博文——线程安全
创建线程的方式?区别?
详细介绍请查阅我的另一篇博文——三种线程创建的方式
线程的状态有哪些?
线程的状态在 JDK 1.5 之后以枚举的方式被定义在 Thread 的源码中,它总共包含以下 6 个状态:
线程状态的源代码如下:
(1)NEW,新建状态,线程被创建出来,但尚未启动时的线程状态;
(2)RUNNABLE,就绪状态,表示可以运行的线程状态,它可能正在运行,或者是在排队等待操作系统给它分配 CPU 资源;
(3)BLOCKED,阻塞等待锁的线程状态,表示处于阻塞状态的线程正在等待监视器锁,比如等待执行 synchronized 代码块或者使用 synchronized 标记的方法;
(4) WAITING,等待状态,一个处于等待状态的线程正在等待另一个线程执行某个特定的动作,比如,一个线程调用了Object.wait()方法,那它就在等待另一个线程调用Object.notify()或 Object.notifyAll() 方法;
(5)TIMED_WAITING,计时等待状态,和等待状态(WAITING)类似,它只是多了超时时间,比如调用了有超时时间设置的方法Object.wait(longtimeout)和Thread.join(long timeout) 等这些方法时,它才会进入此状态;
(6)TERMINATED,终止状态,表示线程已经执行完成。
如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。
线程是如何工作的?(工作过程?)
首先先要创建线程并指定线程需要执行的业务方法,然后再调用线程的start()方法,此时线程就从NEW(新建)状态变成了RUNNABLE(就绪)状态,然后线程会判断要执行的方法中有没有 synchronized 同步代码块,如果有并且其他线程也在使用此锁,那么线程就会变为 BLOCKED(阻塞等待锁的线程)状态,当其他线程使用完此锁之后,线程会继续执行剩余的方法。当遇到Object.wait()或Thread.join()方法时,线程会变为WAITING(等待状态)状态,如果是带了超时时间的等待方法,那么线程会进入TIMED_WAITING(计时等待)状态,当有其他线程执行了notify()或notifyAll()方法之后,线程被唤醒继续执行剩余的业务方法,直到方法执行完成为止,此时整个线程的流程就执行完了。
start() 和run() 方法有什么区别?
源码分析:
(1) start() 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全。它的作用是启动一个新线程。通过start()方法来启动的新线程,从NEW(新建)状态变成了处于RUNNABLE(就绪)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了线程要执行的业务方法,run方法运行结束,此线程随即终止。
源码如下:
(2)run() 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run() 方法其实就是此线程要执行的业务方法;run() 就和普通的成员方法一样,可以被重复调用,如果直接调用run方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行。
源码如下:
Thred实现Runnable接口,重写run方法,源码如下
区别
(1) start() 可以启动一个新线程,让线程从NEW状态转换成RUNNABLE状态,而run()不能,只是一个普通的方法。
(2) start()不能被重复调用(否则会抛出 java.lang.IllegalStateException),而 run() 方法可以进行多次调用,因为它只是一个普通的方法而已。
(3)start()中的run代码可以不执行完就继续执行下面的代码,即进行了线程切换。直接调用run方法必须等待其代码全部执行完才能继续执行下面的代码。
线程的优先级有什么用?该如何设置?
线程的优先级可以理解为线程抢占 CPU 时间片的概率,优先级越高的线程优先执行的概率就越大,但并不能保证优先级高的线程一定先执行。
在程序中我们可以通过 Thread.setPriority() 来设置优先级,setPriority() 源码如下:
线程的常用方法?
join()
详细介绍请查阅我的另一篇博文——等待线程执行终止的 join 方法
yield()
看 Thread 的源码可以知道 yield() 为本地方法,也就是说 yield() 是由 C 或 C++ 实现的,源码如下:
Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示。我们知道操作系统是为每个线程分配一个时间片来占有CPU的,正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了Thread类的静态方法yield时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。
当一个线程调yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权。
Java多线程中调用wait() 和 sleep()方法有什么不同?
详细介绍请查阅我的另一篇博文——Java多线程中调用wait() 和 sleep()方法有什么不同?
Java中notify 和 notifyAll有什么区别?
调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒。
Java中interrupted 和 isInterruptedd方法的区别?
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。
Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
什么是ThreadLocal?
ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程共享它的全局变量,所以这些变量是非线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。每个线程都会拥有他们自己的Thread变量,它们可以使用get()/set()方法去获取他们的默认值或者在线程内部改变他们的值。
如何创建守护线程?
使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。
/** * @author liao.wenhui * @date 2019/7/15 15:13 */ public class DaemonThread { public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { @Override public void run() { } }); //设置守护线程 daemonThread.setDaemon(true); daemonThread.start(); } }
前提知识:
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件(百度百科)。
Java线程分为两类分别为daemon线程(守护线程)和User线程(用户线程),在JVM启动时候会调用main函数,main函数所在的线程是一个用户线程,这个是我们可以看到的线程,其实JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别那?区别之一是当最后一个非守护线程结束时候,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意是只要有一个用户线程还没结束正常情况下JVM就不会退出。
什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的CPU时间分配给可用的Runnable状态的线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(即最好不要让你的程序依赖于线程的优先级)。
在多线程中,什么是上下文切换?
上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。是多任务操作系统和多线程环境的基本特征。
并发编程三要素
(1) 原子性:程序中的所有操作是不可中断的,要么全部执行成功要么全部执行失败。
(2) 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
(3) 可见性:当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。
参考/好文:
Java 面试真题及源码 34 讲
-- https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59
<END>
⭐️希望本文章对您有帮助,您的「 转发、点赞 」是我创作的无限动力。
扫描下方二维码关注微信公众号,您会收到更多优质文章推送。