1.线程有哪些状态
1.1Java线程的六种状态
Java 线程六种状态
新建
当一个线程对象被创建,但还未调用 start 方法时处于新建状态
此时未与操作系统底层线程关联
可运行
调用了 start 方法,就会由新建进入可运行
此时与底层线程关联,由操作系统调度执行
终结
线程内代码已经执行完毕,由可运行进入终结
此时会取消与底层线程关联
阻塞
当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
等待
当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态
有时限等待
当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态
其它情况(只需了解)
可以用 interrupt() 方法打断等待、有时限等待的线程,让它们恢复为可运行状态
park,unpark 等方法也可以让线程等待和唤醒
- 当我们新建一个线程时,这个线程就会进入new新建状态。
- 当我们去调用它的start方法时,线程从新建状态转换为runnable。
- 在可运行状态下,多线程时可能会出现争抢锁的问题,当获取锁失败时,那么线程会从可运行状态进入blocked阻塞状态,暂停当前运行。如果再争抢锁成功了,就会从阻塞状态进入可运行状态。
- 在可运行状态下,如果不满足某些条件导致程序不能正常执行,此时可以使用锁wait() 线程会从可运行状态进入waiting等待状态。
- 当原线程满足条件后,可以在其他的线程中调用notify()来唤醒原线程或其他线程,唤醒后的线程会从waiting等待状态进入可运行状态
- timed_waiting等待状态(有时限),当调用wait(long)方法时,跟wait()不同的时,它使用了一个long类型的参数,这个参数代表了等待的时间,当时间到期后,线程会继续执行从有时限等待进入可运行状态
- 另一方面,有时限等待还可以通过sleep(long)方式来转换,同样也是当设定的时间到了之后,线程自动转换状态到可运行状态。
- sleep(long)和wait(long)在这里的主要区别在于,sleep没有要求它需要满足什么条件,在语义上是我本身就像让这个线程进入等待状态。而wait往往是因为线程没有满足某些条件,比如资源没有分配够,而不得不进入到等待状态。
- 当程序代码全部执行完毕后,线程就进入了terminated终结状态
1.2操作系统层面的五种状态
运行态:分到 cpu 时间,能真正执行线程内代码的
就绪态:有资格分到 cpu 时间,但还未轮到它的
阻塞态:没资格分到 cpu 时间的
涵盖了 java 状态中提到的阻塞、等待、有时限等待
多出了阻塞 I/O,指线程在调用阻塞 I/O 时,实际由 I/O 设备完成,此时线程无事可做,只能干等
新建与终结态:与 java 中同名状态类似,不再啰嗦
2.sleep和wait的异同
一个共同点,三个不同点
共同点
wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
不同点
方法归属不同
sleep(long) 是 Thread 的静态方法
而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
醒来时机不同
执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
它们都可以被打断唤醒
锁特性不同(重点)
wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)