Java 线程02:线程生命周期、Object中唤醒等待机制、生产者与消费者问题、线程的正确停止、线程优先级、守护线程


一、线程状态


1、线程状态(生命周期)概述

当线程被创建并启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程生命周期中,在APIjava.lang.Thread.State这个枚举中给出了6种线程状态 (始于JDK1.5):

线程状态线程状态发生条件
New(新建)尚未启动的线程处于此状态。还未调用start方法
Runnable(可运行)在Java虚拟机中执行的线程处于此状态,可能正在运行自己的代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程视图获取一个对象锁,而该对象锁被其他线程拥有,这时就会进入阻塞状态;当该线程持有锁时,就会进入Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)特定动作时,该线程进入waiting状态,通过锁对象调用Object.wait()。进入这个状态后是不能自动唤醒,必须等待另一个线程通过锁对象调用notify或者notifyAll方法才能被唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入TimedWaiting状态。这个状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep(long timeout)、Object.wait(long timeout)
Terminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获异常终止了run方法而死亡。

在这里插入图片描述

2、Timed Waiting(计时等待)

Timed Waiting状态在线程安全的卖票案例中就已经使用了,待用sleep方法,就时让线程进入计时等待状态,在该状态下就是让线程休眠,不执行任何代码,以”减慢线程“。

在这里插入图片描述

实现一个计时器,计数100,在每个数字之间停1秒,每隔10个数字输出一个字符串。

public class TimedWaiting implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            try {
                System.out.println(i);
                if(i % 10 == 0){
                    System.out.println("--------");
                }
                Thread.sleep(1000); // 进入休眠状态,Timed Waiting
            } catch (InterruptedException e) {
                System.out.println("中断异常");
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new TimedWaiting()).start();
    }
}

3、BLOCKED (阻塞状态)

当多个线程抢夺cpu执行权,当一个线程抢到了cpu执行权,执行run方法,run方法种有同步锁,当该同步锁被其他线程夺走了,那么该线程就不能执行同步代码,所以就会进入blocked阻塞状态。

另外waiting状态和timedwaiting状态被唤醒之后,也不能拿到锁对象,因此也会进入blocked阻塞状态。

直到拿到锁对象,才会进入runnable状态去执行同步代码。

在这里插入图片描述


4、Waiting (无限等待状态)

一个线程在等待另一个线程执行一个(唤醒)特定动作时,该线程进入waiting状态,通过Object调用wait方法进入waiting状态。进入这个状态后是不能自动唤醒,必须等待另一个线程Object调用notify或者notifyAll方法才能被唤醒。也就时线程之间通信,告知你该醒了。

案例 顾客与老板包子

实现一个顾客需要买包子,告知老板我要卖包子,顾客进入等待状态,老板花5秒时间去做包子,做好了之后告知顾客,顾客得到告知,开始吃包子。

代码实现:

// 为什么这里顾客线程永远先拿到锁?
// 因为顾客线程先执行,先start(),所以会先拿到锁去执行自己的类中的run方法
public class WaitingTest {
    public static void main(String[] args) {
        Object obj = new Object(); // 创建一个锁对象,两个线程通过同一个锁对象进行通信交流

        // 创建一个匿名内部类,顾客
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    try {
                        System.out.println("老板,我要买包子。");
                        
                        // 进入无限等待状态,这时cpu执行权就让出去了,并将锁对象释放,必须等待被唤醒,才可以继续执行
                        obj.wait(); 
                        
                        // 顾客线程接收到了老板的通知,被唤醒
                        System.out.println("谢谢老板,你的包子真好吃!"); 
                    } catch (InterruptedException e) {
                        System.out.println("wait()中断异常");;
                    }
                }
            }
        }.start();

        // 创建一个匿名内部类,老板
        new Thread(){
            @Override
            public void run() {
                synchronized (obj){
                    try {
                        // 拿到锁对象,获得cpu执行权,线程进入休眠状态,这过程中不会释放锁对象,花5秒时间做包子,5秒后自动唤醒
                        sleep(5000); 
                        System.out.println("顾客,你的包子做好了。");
                        obj.notify(); // 5秒后,告知顾客线程,你的包子好了,不会释放锁
                    } catch (InterruptedException e) {
                        System.out.println("sleep()中断异常");
                    }
                }
            }
        }.start();
    }
}

5、Object类中的方法

wait()与sleep()区别

都来自不同的类

wait ==》 Object类中

sleep ==》 Thread类中

  • public final void wait(long timeout) throws InterruptedException Object锁对象调用该方法。导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过则会自动唤醒。进入Timed Waiting状态。该方法必须在同步代码块中使用会释放资源会释放锁。
  • public void sleep(long timeout) throws InterruptedException 线程调用该方法 ,进入Timed Waiting状态,时间一过会自动唤醒线程。任何地方使用不释放资源不会释放锁。

wait()和wait(long timeout)区别

  • wait()方法会释放资源释放锁,进入Waiting状态
  • wait(long timeout)方法会释放资源释放锁,进入TimedWaiting状态

sleep()和sleep(long timeout)区别

  • sleep()方法不会释放锁,也不会释放资源,处于runnable的暂停状态
  • sleep(long timeout)方法会释放锁,会释放资源,进入timedWaiting状态,时间内会被notify或者notyall唤醒,超过时间会自动唤醒

notify()和notifyAll()区别

会让进入waiting状态的线程唤醒过来,进入到blocked阻塞状态,等待线程执行完代码之后,释放锁,才会有机会去抢到锁而执行。因此notify和notifyAll是不会立刻释放锁的,只是通知线程你被唤醒,你可以抢夺cpu执行权。只有代码执行完了,该线程才会释放锁。

  • public final void notify() 会随机唤醒等待中的一个线程,先唤醒等待时间久的。

  • public final void notifyAll() 唤醒所有等待Waiting中的线程。


二、线程等待唤醒机制


1、线程之间通信

线程通信也就是线程之间的合作。就如同顾客与老板的案例。

为什么要处理线程间通信:

多个线程是并发执行的,在默认情况下CPU是随机切换线程的,当我们需要多个线程共同完成一件任务时,并且有规律的去执行任务,那么多线程之间就必须得有一些协调通信。

通过等待唤醒机制保证线程间通信有效利用资源。


2、等待唤醒机制

等待唤醒机制是多个线程的一种协作机制。就是一个线程进行了规定操作之后,就进入了等待状态(wait()),等待其他线程执行完他们的指定代码过后,再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有等待的线程。

等待唤醒中的方法

等待唤醒机制就是用于解决线程通信的问题,使用到以下3个方法:

  1. wait:线程不再活动,不再参与调度,进入wait set(等待集合)中,释放锁对象,因此不会浪费CPU资源,也不会去竞争锁,这时的线程状态就是WAITING。他还要的等待别的线程执行一个特别的动作,也就是“通知notify”在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列中。
  2. notify:则选取所通知对象的wait set中的一个线程释放。先唤醒等待时间久的。
  3. notifyAll:则释放所有通知对象的wait set上的全部线程。

注意:

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻锁对象已经被其他线程占有,所以它需要再次尝试获取锁对象(很可能面临其他线程的竞争),成功之后才能开始恢复执行当初调用wait方法之后的代码。

总结:

  • 如果能获取锁,线程就能从WAITING状态变为RUNNABLE状态
  • 如果不能获取锁,从wait set出来,进入 entry set(进入集合) ,线程从WAITITNG状态又变成BLOCKED状态

notify和wait方法的使用注意事项:

  1. notify和wait方法都必须使用同一个锁对象,因为notify只能唤醒同一个锁对象的线程。
  2. notify和wait方法都必须在同步代码块或同步方法中调用,可以保证锁对象唯一。
  3. notify和wait方法必须是Object类中的方法。因此所有的类对象都可以成为锁对象。

练习:生产者与消费者问题

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

包子铺线程生产包子,吃货线程消费包子。当包子没有时,吃货线程等待,包子铺线程生产包子,
并通知吃货线程,因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进
一步执行取决于锁的获取情况。如果吃货获得锁,那么就执行吃包子的动作,包子吃完了,并通
知包子铺线程,吃货线程进入等待,包子铺线程能否进一步执行则取决于锁的获取情况。

在这里插入图片描述

Baozi.java

public class Baozi {
    private String pi; // 包子皮
    private String xian; // 包子馅
    private boolean zhuangTai = false; // 判断是否有包子,初始化没有包子

    public String getPi() {
        return pi;
    }

    public void setPi(String pi) {
        this.pi = pi;
    }

    public String getXian() {
        return xian;
    }

    public void setXian(String xian) {
        this.xian = xian;
    }

    public boolean isZhuangTai() {
        return zhuangTai;
    }

    public void setZhuangTai(boolean zhuangTai) {
        this.zhuangTai = zhuangTai;
    }
}

Shengchan.java

public class Shengchan implements Runnable{
    // 创建一个锁对象
    private Baozi baozi;

    public Shengchan(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        int count = 0; // 定义一个变量,用于生产不同的包子
        while (true){
            synchronized (baozi){
                if(baozi.isZhuangTai()){ // 如果有包子,就等待
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        System.out.println("生产wait()有异常!");
                    }
                }
                // 如果没有包子就生产包子
                if(count % 2 == 0){
                    baozi.setPi("薄皮");
                    baozi.setXian("三鲜馅");
                }else {
                    baozi.setPi("厚皮");
                    baozi.setXian("大葱牛肉馅");
                }
                count++;

                System.out.println("请稍等,正在为你生产包子...");
                try {
                    Thread.sleep(5000); // 让线程等待5秒钟,假设用5秒在生产包子
                } catch (InterruptedException e) {
                    System.out.println("生产sleep()有异常!");
                }
                System.out.println("你的"+ baozi.getPi() + baozi.getXian() + "包子,已为你生产完成,请享用!");

                // 将包子的状态设置为有包子,并通知等待的线程
                baozi.setZhuangTai(true);
                baozi.notify();

            }
        }
    }
}

Xiaofei.java

public class Xiaofei implements Runnable {
    // 创建一个锁对象
    private Baozi baozi;

    public Xiaofei(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baozi){
                if(baozi.isZhuangTai()){ // 如果为true就是有包子,就吃包子
                    System.out.println("正在吃" + baozi.getPi() + baozi.getXian() + "包子...");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        System.out.println("消费sleep()有异常!");
                    }

                    baozi.setZhuangTai(false); // 将包子设为false表示没有包子了
                    System.out.println("包子已吃完,老板我还要包子。");
                    System.out.println("-------------------------------------");
                    try {
                        baozi.notify(); // 通知等待的生产线程,你要准备生产把包子了
                        baozi.wait();
                    } catch (InterruptedException e) {
                        System.out.println("消费wait()有异常!");
                    }
                }else {
                    baozi.notify(); // 通知等待的生产线程,你要准备生产把包子了
                }
            }
        }
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        Baozi baozi = new Baozi(); // 创建一个包子类
        // 将包子类给两个线程,保证了锁对象一致,两个线程也可以对同一资源(同包子实例)进行处理
        new Thread(new Shengchan(baozi)).start(); 
        new Thread(new Xiaofei(baozi)).start();
    }
}

三、线程正确停止

方法:

  1. 利用次数,不建议死循环
  2. 建议使用标志位,如建立flag通过判断true,false
  3. 不建议使用JDK的stop方法,interrupt方法,destroy放等一些过时的方法
public class CallableTest implements Runnable {
    private boolean flag = true;

    @Override
    public void run() {
        while (flag){
            System.out.println("线程run");
        }
    }

    public void stop(){
        flag = false;
    }

    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        new Thread(callableTest).start();
        for (int i = 0; i < 10000; i++) {
            if(i == 5000){
                callableTest.stop();
                System.out.println("线程被停止了");
            }
            System.out.println(i);
        }
    }
}

四、线程优先级

线程优先级用数字表示,范围1~10

注意:

不是优先级高的就能够被优先执行,执行结果还是的看cpu的调度。

优先级高了其实就是增加了权重,被执行的几率就大了很多。

Thread.MAX_PRIORITY = 10;

Thread.MIN_PRIORITY = 1;

Thread.NORM_PRIORITY = 5;

线程调用方法获取和设置优先级:

  • getPriority()
  • setPriority(int num)

超过最大值、最小值会报异常


五、守护(daemon)线程

  • 线程分为守护线程(后端线程)用户线程(前端线程)

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕,如:后台记录日志,监控内存,垃圾回收(GC)等

用户线程执行完了,程序就结束了。不用管守护线程。

自己创建的正常线程,main线程都是用户线程。

调用方法设置守护线程:

  • setDaemon(boolean flag)

传值为true,则设置为守护线程。默认为false。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值