并发编程<一>线程5大状态切换时机分析及sleep,join,wait,notify,notifyAll,yield剖析

本文详细分析了Java线程的5大状态,包括新建、就绪、运行、阻塞和终止状态。重点讨论了阻塞状态及其原因,如sleep、join、wait、notify、notifyAll和yield等方法的使用和区别。通过实例展示了这些方法如何影响线程的执行和调度,强调了wait方法释放锁和sleep方法不释放锁的区别,以及join方法的特性验证。
摘要由CSDN通过智能技术生成

线程5大状态分析


上图是线程从创建到消亡的一个切换过程。下面我们简单类分析每一个状态。

  1. 新建状态:新建状态具体是指调用new Thread()创建出线程对象,但是还没有调用start方法的这段时间。前面的一篇文章《Java虚拟机剖析之内存区域,内存的溢出,泄漏》一文中有说到,每一个线程都会有自己的私有内存区域。处于新建状态下的线程,此时还未分配系统资源,也即是还没有分配到私有内存。
  2. 就绪状态:start方法刚被调用的一段时机。处于当前状态的线程已经分配到所需资源,但是还没有获得CPU使用权,在此状态的线程会相互竞争CPU使用权。
  3. 运行状态:被os选中,获得CPU使用权,开始执行任务,也即是开始运行run/main方法。
  4. 阻塞状态:在执行任务的过程中由于一些原因导致线程阻塞(后面会重点讲阻塞状态,这也是本文重点)。
  5. 终止状态:任务执行完毕(run/main方法执行完毕)或者线程异常终止。

阻塞状态,阻塞原因

阻塞状态对我们开发人员来说是最关键的一个状态,因为我们能通过各种造成阻塞的手段合理的调度指定的线程执行特定的任务,能准确的控制每一个任务执行的时机。造成阻塞的原因大致为下面4种:

  1. 调用sleep/join方法
  2. 调用wait方法
  3. 访问临界资源时(如synchronized字段修饰的方法或者代码块),等待竞争锁对象所有权
  4. I/O导致阻塞(比如:等待用户输入)
其中这四种方式中1,2两种阻塞可以中断,3,4两种不会对唤醒线程的操作有反应。

关键方法分析sleep,join,wait,notify,notifyAll,yield

  1. sleep方法是Thread的静态方法,当该方法调用时,会让调用的线程进入阻塞状态,直到历时sleep的参数时间后唤醒该线程。当线程在持有临界资源对象锁持有权时调用sleep方法,线程进入阻塞,不会释放所持有的对象锁持有权,容易造成死锁。
  2. join方法是Thread的公有方法,归对象所有。在A线程中调用B线程的join方,A线程进入阻塞状态,知道B线程的任务执行完毕才会唤醒A线程继续执行未完成的任务。
  3. wait方法是超类Object的方法,该方跟notify/notifyAll配套使用,这一对方法用于线程间通讯控制并发。这一对方法需要线程在持有临界资源对象锁所有权的情况下才能调用,否则抛出IllegalMonitorStateException。调用wait方法的线程交出CPU使用权进入阻塞状态,需等到竞争同一锁对相的线程调用notify/notifyAll方法才会被唤醒(notify是在所有相关的处于等待状态的线程中随机选择一个线程唤醒,notifyAll是将所有相关的处于等待状态的线程全部唤醒),然后进入锁池,重新竞争对象锁的持有权。wait方法调用的线程会释放临界资源对象锁的持有权。
  4. yield方法是Thread的静态方法,调用该方法的线程相当于线程的时间片用完,回到就绪状态,重新竞争跟同等优先级线程CPU使用权(直观的说就是A,B线程为同等优先级线程,同时开始竞争CPU使用权。A线程获取到CPU使用权进入运行状态,正在执行任务,run方法运行到一半的时候调用了yield方法,此时A线程将会跟B线程再一次平等继续竞争CPU使用权,如果A得到使用权,会继续刚才完成到一半的任务继续未完成的任务执行完)
  5. 另外suspend和resume方法配对使用,跟wait和notify/notifyAll这一对差不多,调用suspend的线程进入阻塞,需要等到对应的resume方法被调用才会唤醒。但是有一个区别是suspend方法调用的线程会释放对象锁的持有权。这对方法不经常用,所以略过。。。

sleep和wait方法的区别及特性验证

共同点:

  1. 都能是线程进入阻塞状态
  2. 都可以设置阻塞时间
不同点:

  1. 唤醒方式不同,sleep方法是在指定的时间之后自动唤醒。wait必须等到别的相关线程调用notify/notifyAll才会唤醒(当然了,wait(long time)方法也能指定等待时间,等待时间到了之后还未调用notify/notifyAll将会自动唤醒,特殊情况)
  2. sleep调用时不一定需要线程持有临界资源的对象锁,但是wait方法的调用线程必须持有临界资源的对象锁,否则会抛出异常。
  3. 调用sleep方法进入阻塞状态后不会释放持有的对象锁,但是wait方法会释放所持有的对象锁(主要区别)
这些区别导致了控制线程的方式完全不同,使用的场景也不相同,我们用实例能直观的验证sleep和wait的特性以及区别。下面用生产者/消费者模式进行说明验证,其实真正的生产者/消费者模式需要用的是wait方法,用sleep方法有可能造成死锁,这儿只是为了证明两种方法的区别!

工厂:

package com.example;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by PICO-USER on 2017/11/7.
 */

public class FactoryClass {
    //用于存放产品的容器
    public List<String> products = new ArrayList<>();

    /**
     * 生产量是否已经达到饱满
     *
     * @return true 表示已经饱满
     */
    private boolean isFull() {
        return products.size() >= 40 ? true : false;
    }

    /**
     * 库存是否已经为0
     *
     * @return true 表示已售完
     */
    private boolean isEmpty() {
        return products.size() <= 0 ? true : false;
    }

    /**
     * 商店卖东西,调用wait进入阻塞
     *
     * @param consumer
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值