1.线程的六种状态
NEW(创建)
线程已创建但还未执行start()方法;
Runnable(可运行)
线程调用start()方法便会进入Runnable状态;
备注:线程运行中的状态也是Runnable
Blocked(阻塞)
当一个线程进入到被synchronized修饰的代码块时,并且该锁已经被其它线程拿走时,
此时线程的状态就为Blocked;
Waiting(等待)
线程调用无参wait()或join()等方法时会进入Waiting(进入无期限等待,等待被唤醒);
Timed Waiting(计时等待)
线程调用有时间参数sleep(time),wait(time)或join(time)等方法时会进入Time Waiting;
Terminated(终止)
run()方法执行完毕 或 出现了一个未被捕获的异常导致这个run()方法意外被终止;
2.Thread和Object类重要方法详解
2.1.wait()/notify()/notifyAll()方法详解
用于控制一些线程休息或被唤醒(用于实现“线程通信” 或 “阻塞线程”)
2.1.1.wait()方法
wait()/notify()/notifyAll()方法都必须放在synchronized代码块中执行,执行wait()方法时必须先拥有这个对象的monitor锁;调用wait()方法的线程会进入阻塞状态并且只会释放当前的monitor锁;
/**
* 此类用于展示 wait/notify的基本用法
* 目的:
* 研究代码执行顺序
* 证明wait释放锁
*/
public class Wait {
public static Object obj = new Object();
static class Thread1 extends Thread{
@Override
public void run() {
synchronized (obj){
System.out.println("线程:" + Thread.currentThread().getName() + "开始执行");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "获取到了锁");
}
}
}
/**
* 用于唤醒线程
*/
static class Thread2 extends Thread{
@Override
public void run() {
synchronized (obj){
obj.notify();
System.out.println("线程:" + Thread.currentThread().getName() + "调用了notify方法");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Thread1());
Thread t2 = new Thread(new Thread2());
t1.start();
Thread.sleep(200);
t2.start();
}
}
2.1.2.notify()/notifyAll()方法
执行notify()/notifyAll()方法时必须先获取monitor锁监视器,所以只能在synchronized修饰的代码中使用;
notify():随机唤醒一个正在等待获取这个“obj”对象的锁监视器的线程;
notifyAll():唤醒所有正在等待获取这个“obj”对象的锁监视器的线程;
/**
* 此类用于展示 notify/notifyAll的基本用法及区别
*/
public class WaitNotifyOrNotifyAll implements Runnable{
public static Object obj = new Object();
@Override
public void run() {
System.out.println("线程:" + Thread.currentThread().getName() + "开始执行");
synchronized (obj){
try {
System.out.println("线程:" + Thread.currentThread().getName() + "准备执行wait");
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "执行结束..");
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new WaitNotifyOrNotifyAll());
Thread t2 = new Thread(new WaitNotifyOrNotifyAll());
t1.start();
t2.start();
Thread.sleep(200);
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
obj.notify(); // 随机唤醒一个正在等待获取这个“obj”对象的锁监视器线程
// obj.notifyAll(); // 唤醒所有正在等待获取这个“obj”对象的锁监视器线程
}
}
});
t3.start();
}
}
3.sleep()方法详解
作用:
只想让线程在预期的时间执行,其它时候不要占用CPU资源;
特点:
不释放锁
1.包括synchronized和lock
2.和wait不同(wait会先释放锁再等待,sleep不释放锁直接等待)
sleep方法响应中断
1.抛出InterruptedException
2.清除中断状态
总结:
sleep()方法可以让线程进入Waiting状态并且不占用CPU资源,但不释放锁直到指定的休眠时间过后再继续执行,休眠期间如果被中断会抛出InterruptedException异常;
4.join()方法详解
作用:
因为新的线程加入到了我们,所以我们要等待它执行完再出发;(主线程等子线程)
用法:
主线程等待join进来的子线程执行完毕再执行(注意谁等谁);
原理:
当执行thread.join()时join内部会调用wait()方法,即此时主线程会进入休眠,当子线程run()方法执行完毕后其Thread类底层会调用notifyAll()方法,故此时主线程被唤醒继续执行;
普通用法
import java.util.concurrent.TimeUnit;
/**
* 演示join,注意输出语句变化
* 语句输出:
* 线程开始执行
* 线程开始执行
* 线程执行完毕
* 线程执行完毕
* 主线程执行完毕
*/
public class Join {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程开始执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行完毕");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程开始执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行完毕");
}
});
thread.start();
thread2.start();
try {
thread.join(); // 主线程会等thread执行完毕再执行(thread线程加入主线程,故主线程会等待thread线程执行完毕再执行)
thread2.join(); // 主线程会等thread2执行完毕再执行(thread2线程加入主线程,故主线程会等待thread2线程执行完毕再执行)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行完毕");
}
}
遇到中断
在join时捕捉到的中断信号此时中断的是主线程;
import java.util.concurrent.TimeUnit;
/**
* 演示join期间被中断(中断的是主线程,因为是主线程在等子线程执行完毕)
*/
public class JoinInterrupt {
public static void main(String[] args) {
// 拿到主线程的引用
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();// 在子线程中对主线程进行中断
TimeUnit.SECONDS.sleep(5); // 休眠5s
System.out.println("子线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("子线程被中断");
}
}
});
thread.start();
System.out.println("等待子线程运行完毕");
try {
thread.join(); // 子线程加入主线程,主线程等待子线程执行完毕所以此时如果发生中断,则中断的是主线程(所以会有在子线程中执行mainThread.interrupt();去模拟中断主线程,让主线程在执行thread.join();时收到这个中断信息)
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主线程被中断了");
thread.interrupt(); // 当主线程被中断时应同时将这个中断信号传递给子线程,让子线程也中断
}
System.out.println("执行完毕");
}
}
注意点
JDK在CountDownLatch和CyclicBarrier类中提供了类似join功能的封装
5.yield方法详解
作用:
释放当前线程的CPU时间片,但不释放锁;
定位:
JVM执行此方法时并不保证此方法的执行效果,即JVM执行完yield()方法后JVM并不一定就会释放线程的CPU时间片;执行完yield()方法后线程状态还是Runnable(可运行状态)
面试题
1. 线程有哪几种状态,生命周期是怎么样?
创建->可运行->阻塞->等待->计时等待->终止
线程的执行状态只能由 “新建” 到 “可运行” 到 “终止”这三个状态不能跳跃执行,其中线程可以
在“阻塞”,“等待”,“计时等待”这三种状态中来回执行;
2.wait()/notify()/notifyAll()常见面试题
2.1.1.两个线程交替打印奇偶数(笔试)
/**
* 使用wait()/notify()实现两个线程交替打印0-100的奇偶数
*/
public class WaitNotifyPrintOddEveWait {
// 计数器
private static int count = 0;
// 锁对象
private static Object lock = new Object();
public static void main(String[] args) {
new Thread(new TurningRunner(),"线程一").start();
new Thread(new TurningRunner(),"线程二").start();
}
static class TurningRunner implements Runnable{
@Override
public void run() {
synchronized (lock){
while (count <= 100){
System.out.println(Thread.currentThread().getName() + "打印:" + count++);
lock.notify(); // 先唤醒其它线程再wait休眠(如果先wait休眠则容易出现两个线程都在休眠,出现“永久等待”的情况发生)
try {
/**
* 详解:if(count <= 100) lock.wait();代码..
* 最后一次循环:count = 100
* 1.进入循环
* 2.执行输出语句,此时打印的count++ 仍为100
* 3.执行lock.notify()唤醒其它线程
* 4.判断count <= 100,因上输出语句执行了count++,故此时的count = 101
* 5.101 <= 100 不成立故不再调用wait方法进入等待,又因上面执行了lock.notify()故两个线程至此都执行结束
*/
if(count <= 100) lock.wait(); // 此处为线程出口(如果任务还没结束就让出当前的锁,并且当前线程进入休眠)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
2.1.2.使用wait()和notify()方法实现生产者消费者模式(笔试)
解释可参考:《Java面向对象编程》导读-多线程的通信,wait()和notify(),生产者和消费者的通信 - 知乎
import java.util.Date;
import java.util.LinkedList;
/**
* 使用wait() 和 notify()实现生产者消费者模式
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
// 模拟存储数据队列
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
/* 队列 */
class EventStorage{
// 容量
private int maxSize;
// 容器
private LinkedList<Date> storage;
// 构造初始化
public EventStorage(){
maxSize = 10;
storage = new LinkedList<>();
}
/* 添加数据到队列 */
public synchronized void put(){
while (storage.size() == maxSize){ // 添加的产品超过了“队列”的最大容量,则先进入等待(wait)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("仓库中已有:" + storage.size() + "个产品");
// 每成功添加一个对象就调用notify去随机唤醒一个等待获取锁的线程,此处唤醒消费者(若没有可唤醒的线程,程序继续执行不会有异常信息)
notify();
}
/* 消费队列中的数据 */
public synchronized void take(){
while(storage.size() == 0){ // 已经消费完数据
try {
wait(); // 无数据可消费则进入wait,等待有数据时被唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/* storage.poll();代表取出数据且将这个取出的数据从storage中移除 */
System.out.println("拿到了:" + storage.poll() + "产品,还剩下:" + storage.size() + "个产品");
notify(); // 唤醒生产者
}
}
/* 生产者 */
class Producer implements Runnable{
private EventStorage storage;
// 构造传参
public Producer(EventStorage storage){
this.storage = storage;
}
@Override
public void run() {
for (int i = 1; i <= 100 ; i++) {
storage.put();
}
}
}
/* 消费者 */
class Consumer implements Runnable{
private EventStorage storage;
// 构造传参
public Consumer(EventStorage storage){
this.storage = storage;
}
@Override
public void run() {
for (int i = 1; i <= 100 ; i++) {
storage.take();
}
}
}
2.1.3.为什么wait()/notify()/notufyAll()方法需在同步代码块中使用而sleep()不需要?
wait/notify/notifyAll方法若不放在synchronized修饰的代码块或方法中执行编译时不会报错,执行时会抛出IllegalMonitorStateException异常(非法的锁状态异常);
放在同步代码块中执行原因在于确保线程调用这些方法时已经获取到对象锁,如果不在同步代码块中来实现互斥则会出现“死锁”或永久性等待(在执行wait()方法之前notify/notifyAll已经执行完毕,当再执行wait()方法后因为notify/notifyAll方法执行完毕,则此时出现“死锁”);而sleep()本身是针对线程自己的和其它线程关系并不大所以不需要放在同步代码块中执行;
2.1.4.为什么线程通信方法wait()/notify()/notifyAll()方法定义在Object类中而sleep()方法定义在Thread类中?
因为wait()/notify()/notifyAll()方法是属于“锁”级别的操作,而锁是属于某一个对象的(即:锁是绑定在某个对象中而不是线程中[每个对象头中前几位都是用来保存锁的状态]),如果将wait()、notify()、notifyAll()定义在Thread类中则有很大的局限性(假如wait()方法定义在Thread类中当一个线程持有多把锁时调用wait()方法就不能实现很灵活的逻辑,比如在释放锁时应该释放哪把锁;)
2.1.5.wait()方法属于Object对象的,那调用Thread.wait()会怎么样?
Thread是Object的子类,Thread类非常特殊当线程退出时底层会自动执行notifyAll()方法,试想如果使用Thread.wait()方法当线程退出时会执行notifyAll()方法唤醒其它线程,这样会使我们原本设计流程受到影响;
2.1.6.如何选择用notify()还是notifyAll()?
主要看是想随机唤醒一个线程,还是想唤醒所有线程;
2.1.7.比较wait()/notify()/sleep()方法的异同
相同:
1.都阻塞(wait/notify在同步代码块中执行所以会阻塞);
2.都响应中断;
不同:
1.wait()/notify()需在同步代码块中执行,sleep()不需要;
2.wait()会释放锁,sleep()不会释放锁;
3.wait()不传参时需等待被唤醒才能继续执行,sleep()需指定休眠时间待休眠完成后继续执行;
4.wait()/notify()方法归属于Object类,sleep()归属于Thread类;
2.1.8.在join期间线程处于哪种状态?
Waiting状态;
2.1.9.join()和wait()之间的关系
join的底层还是在调wait()方法;
2.1.10.yield和sleep区别
yield:执行yield方法后能够再次被CPU调度;
sleep:执行sleep方法后在休眠期间不会再次被CPU调度;