线程有哪些基本状态,并描述每种状态
新建状态(NEW)
新创建了一个线程对象
就绪状态(RUNNABLE)
当其他线程调(main主线程)调用该线程的start()方法时,该线程进入就绪状态,等待CPU的调度
运行状态(RUNNING)
CPU调度处于就绪状态中的线程 (处于就绪状态中的线程获得CPU资源时),该线程开始执行线程体代码 —— run()方法
阻塞状态(BLOCK)正在运行的线程遇到特殊情况时,如sleep()、同步、等待I/O操作完成等,该线程会进入阻塞状态,并让出CPU资源,直到线程进入就绪状态,才有机会转到运行状态
- 休眠状态
当运行线程调用sleep()方法时,进入休眠状态,睡眠时间一过进入就绪状态 - 等待状态
当处于运行状态的线程调用Thread类中的wait()方法时,该线程进入等待状态,而等待状态的线程必须通过另一个线程调用Thread类中的notify()方法才能被唤醒进入就绪态执
- 同步阻塞
执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则转入阻塞状态
- 其他阻塞
如等待I/O操作完成等,该线转为堵塞状态,等处理完成时,线程又一次转入就绪状态
死亡状态(DEAD)
当线程成功执行完成( run()方法执行完成 )、线程抛出异常或者调用线程的stop方法退出run()方法时,表示该线程结束生命周期
什么是线程死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放, 由于线程互相等待释放资源而无期限地阻塞,最终导致程序不能正常终止
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
产生死锁必须具备以下四个条件:
- 互斥条件 :该资源任意一个时刻只能被一个线程占用。
- 请求与保持条件 :当一个进程请求其他资源而进入阻塞状态时,不释放本身已获得的资源
- 不剥夺条件 : 线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件 : 多个进程之间形成闭环等待资源关系。
如何避免线程死锁?
我上面说了产生死锁的四个必要条件,为了避免死锁,我们只要破坏产生死锁的四个条件中的其中一个就可以了。现在我们来挨个分析一下:
1. 破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)
2. 破坏请求与保持条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
3. 破坏不剥夺条件
一次性申请所有的资源。
4. 破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?
Thread类中的join()方法 —— 线程插队
例如在主线程中,线程T1调用start()方法,join(),接着T2调用start()方法,join()方法,最后线程T3调用start(),join()方法
主线程中调用了 T1 线程的 join()方法 ,相当于 T1线程 调用了主线程中的 wait() 方法,当 T1 线程执行完,T1线程会自动调用自身的notifyAll方法唤醒主线程,从而达到同步的目的
说说 sleep() 方法和 wait() 方法区别和共同点?
相同点
Sleep()方法和wait()方法都用来改变线程的状态
区别
线程状态转换的不同
调用sleep()方法和带有超时参数的wait()方法,会让线程状态从运行状态转变为休眠状态,sleep() 方法和带超时参数的wait()方法执行完成后,线程会自动苏醒。
调用无参的wait()方法,会让线程状态从运行状态转变为等待状态。 wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒
暂停线程是否释放锁
sleep 方法没有释放锁,而 wait 方法释放了锁使用约束
任何线程都可以调用sleep()方法进入休眠状态,但是只有获取synchronized 隐式锁 的线程,才能调用wait()方法使用场景
同时wait()方法主要用于等待/通知机制,除了超时机制,还能通过调用notify()和notifyAll()让线程状态转变为就绪状态RUNNABLE