Java并发编程:Thread类的使用

一:线程的状态

在正式学习Thread类之前,我们先了解一下Java中线程的状态。面试中也经常被问到Java中线程有几种状态?Thread类中的内部枚举State给出了答案(这里拷贝源码的时候省去了注释):

    public enum State {
       
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

可以清楚的看到一共有六种状态。线程从创建到运行最终消亡要经历若干个状态。一般来说主要的流程是:创建(new),就绪(Runnable),阻塞(blocked),Waiting,Time_waiting,消亡Terminated.

当需要新启动一个线程来执行某个子任务时,就创建了一个线程,但是创建线程后不会立即进入就绪状态。需要调用Thread的start()方法后才进入就绪状态,然后一切准备就绪后就等待CPU的时间调度。

当线程获得CUP的执行时间片以后就进入运行状态。

运行状态过程中有很多原因可能导致线程进入阻塞状态(比如调用sleep,wait方法等),这个时候线程的状态可能是BLOCKED,WAITING,TIME_WAITTING状态。最后得到时间片继续执行,依次循环,知道任务执行完毕后线程状态为TERMINATED。

具体流转可以如下图所示:

 

二:上下文切换

对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。

虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。并不是线程越多越好。

 

三:Thread类中的方法

Thread类实现了Runnable接口,在Thread类中,有一些比较关键的属性,比如name是表示Thread的名字,可以通过Thread类的构造器中的参数来指定线程名字,priority表示线程的优先级(最大值为10,最小值为1,默认值为5),daemon表示线程是否是守护线程,target表示要执行的任务。

下面是线程中常用的方法:

  1. start方法:用来启动一个线程,当调用start()方法后通知操作系统的“线程规划器”此线程已经准备就绪,等待调度该线程对应的线程对象的run()方法,执行run()方法中定义的任务。
  2. run方法:run()不需要用户调用,而是调用start()方法启动一个线程后,当线程获得了CPU执行时间片后进入该方法执行的具体任务,继承Thread类后必须重写run方法,在里面定义需要执行的任务。
  3. sleep方法:sleep方法让当前线程睡眠,交出cpu,让cpu去执行其他事情; 但是有一点需要注意sleep方法不会释放锁,也就是说如果当前线程持有了某个对象的锁,计时调用sleep方法,其他线程也无法访问这个对象的这个方法。这个后面具体写文章介绍了。
  4. yield方法:调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

    注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

  5. join方法:join方法有三个重载版本:

    join();
    join(long millis); //参数为毫秒
    join(long millis, int nanos); //第一个参数为毫秒,第二个参数为纳秒

    假如在main方法中调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一段时间。如果调用你的是无参join方法,则等待thread线程执行完毕,如果调用指定了时间的方法则等待一定的时间。

  6. interrupt方法:interrupt方法用来将线该线程的中断标志置为true, interrupt方法结合isInterrupt方法可以用来中断线程,独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。具体参考: https://www.cnblogs.com/dolphin0520/p/3920357.html 

  7. stop方法:stop方法已经是一个废弃的方法,它是一个不安全的方法。因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。

  8. getId方法:获取当前线程的唯一id

  9. getName方法:获取线程名字

  10. getPriority和setPriority:设置和获取线程优先级

  11. setDeamon和isDeamon:用设置线程是否是守护线程和判断线程是或否是守护线程, 守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

  12. Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。

在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?下面一幅图就是在上面的图上进行改进而来的:

 

参考链接: https://www.cnblogs.com/dolphin0520/p/3920357.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值