• 深入理解多线程(三)


    深入理解多线程(三)

    在前两篇博客中深入理解多线程(一)、深入理解多线程(二)中分别介绍了:多线程在jvm内存图中的状态、多线程的两种实现方式、多线程的安全;接下来介绍线程的状态。

    1 线程状态概述

    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,

    有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:

    这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析

    线程状态 导致状态发生条件
    New(新建) 线程刚被创建,但是并未启动。还没调用start方法
    Runable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器
    Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runable状态
    Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
    Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting 状态。这一状态将一直保持到朝时期满或者接受到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait
    Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡

    这里不再研究这几种状态的实现原理,而是研究这几种状态的转换问题。

    1.1 Timed Waiting(计时等待)

    这里的计时等待,其实是显而易见的,我们平常写代码的时候也经常用到,就是Thread.sleep(),其实 当我们调用了sleep方法后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting ,接下来将通过一个案例加深对该状态的一个理解。

    public class MyThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("i:"+i);
            }
        }
    }
    

    通过案例可以发现,sleep方法的使用还是很简单的。我们需要记住下面几点:

    1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协

    作关系。

    1. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程

    中会睡眠

    1. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。

    小提示:sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。

    Timed Waiting 线程状态图:

    1.2 BLOCKED(锁状态)

    Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

    知道了多线程的同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获 取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

    这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态

    1.3 Waiting (无限等待)

    Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

    无限等待一般情况下是很少遇到的,但并不妨碍我们进行一个简单深入的了解。我们通过一段代码来学习一下:

    public class MyThread{
    
        public static Object object=new Object();
    
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        synchronized (object){
                            System.out.println(Thread.currentThread().getName()+"获取到锁对象,调用wait方法,进入waiting,释放锁对象");
                            try {
                                //无限等待
                                object.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"从waiting状态醒来,获取锁对象,继续执行");
                        }
                    }
                }
            },"等待").start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        System.out.println(Thread.currentThread().getName()+"等待3秒钟");
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (object){
                            System.out.println(Thread.currentThread().getName()+"获取锁对象,调用notify方法,释放锁对象");
    
                            object.notify();
                        }
                    }
                }
            },"唤醒").start();
        }
    }
    

    通过上述案例我们会发现,一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的

    Object.notify()方法 或 Object.notifyAll()方法。

    其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,

    多个线程会争取锁,同时相互之间又存在协作关系。就好比在公司里你和你的同事们,你们可能存在晋升时的竞

    争,但更多时候你们更多是一起合作以完成某些任务。

    当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入

    了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了

    notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入

    Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。

    总结:线程状态转换图

    Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,

    比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。

    这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们设定好时间,到时通知,可是 如果提前得到(唤醒)通知,那么设定好时间在通知也就显得多此一举了,那么这种设计方案其实是一举两得。如果没有得到(唤醒)通知,那么线程就处于Timed Waiting状态,直到倒计时完毕自动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从Timed Waiting状态立刻唤醒。

  • 相关阅读:
    1.5 判断是奇数还是偶数(比较运算符 & if...else)
    1.4计算器 (数字类型 & 算术运算符 &赋值运算符)
    1.3hello 张三(终端交互 & 字符串)
    1.2python语言环境 & python IDE(集成开发环境)搭建
    Centos 7 下安装jdk 7
    Centos 7 安装redis
    git 学习笔记
    git 基础操作
    git 获取远程分支的代码
    redis基础操作
  • 原文地址:https://www.cnblogs.com/jimlau/p/12463663.html
Copyright © 2020-2023  润新知