线程状态转换
线程是由内核自动调度的运行在进程上下文中的逻辑流。
基本概念
线程有自己的上下文:唯一的Thread ID、栈、栈指针、程序计数器、通用目的寄存器、条件码。
并发与并行
-
并发:在单核多任务或者任务数大于核数的情况下,对于某个cpu来说,会把cpu执行时间分为几个时间片,交替运行任务。在一个时间片内执行某个任务后(可能没执行完),会挂起当前的任务然后执行其他的任务。挂起前会保留线程的上下文以便从中断点恢复cpu状态,例如:cpu寄存器(记录挂起前变量值等等),程序计数器(记录线程执行到哪条指令了)等等。
-
并行:多核情况下,两个任务分别在不同cpu执行,两个任务互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
分类
-
守护线程
在操作系统中,是没有守护线程概念的,只有守护进程。jvm借鉴了unix守护进程的概念,构建了对自己有利的守护线程。有以下特点:- 为用户线程服务的
- 优先级较低,只剩下守护线程的话,jvm就会退出
- 典型的例子GC Daemon,当就剩下GC Daemon时,就没有垃圾产生,就没有存在的必要了,jvm就会退出
- 在Daemon线程中产生的新线程也是Daemon的,而守护进程fork()出来的子进程不再是守护进程。
-
用户线程:非守护线程
线程的状态
分类
-
新建(New):准备线程执行需要的一些条件,例如线程自己的一些上下文
-
就绪(Runnable):线程准备好的条件满足后进入就绪状态或者Running的时间片到了也会进入就绪状态
-
运行 (Running):就绪状态的线程分配到了cpu时间片
-
定时等待(Timed Waiting):时间内的等待
-
等待(Waiting):主动睡眠,等待被唤醒
-
阻塞(Blocked):IO阻塞或者等待锁
-
终止(Terminated):死亡
线程运行中会被某些原因所打断进入阻塞(详细分为Timed Waiting,Waiting,Blocked)或者Runnable(时间片到了)
线程状态的转换
状态转换图- start()方法标识启动一个线程,并分配资源。
- yield()交出时间分片让高优先级的线程先执行,如果持有锁的话是不会释放掉的。
- sleep()方法会交出时间分片,如果持有锁的话是不会释放掉的。
- Object.wait()交出cpu时间,并且释放掉锁。
- 使用wait(),notify(),notifyAll()前先拿到锁后才能执行,因为方法或者代码块执行完才能释放锁,所以在方法里使用notify()或者notifyAll()后最好不要跟太耗cpu时间的计算。
- new Object().wait() 编译不会报错,但运行会报java.lang.IllegalMonitorStateException
- join()调用了Object.wait()方法。
- interrupt()可以中断正在处于阻塞(Waiting,Timed Waiting,不包括Blocked)的线程,使阻塞线程抛出InterruptedException,sleep、wait、join这些方法都会抛出InterruptedException。 interrupt()会把中断标志位置为true。
- interrupt()不可中断不处于阻塞的线程,但是interrupt()后如果用isInterrupted()来判断标志位(是否中断)也可以达到目的。
- 有些操作是不适合用Daemon线程的,例如读写操作,因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
- thread.setDaemon(true)必须在start()之前设置,否则会抛IllegalThreadStateException异常。
因为Thread类用了代理模式,实现了Runnable接口的run方法。
可以使用new Thread(被代理的Runnable实例).start(); 执行的是被代理类的run;
当然也可以直接继承Thread覆写run方法。
下篇详细剖析下JCU里的老大-AQS,请大家保持关注哦。