【JAVA核心知识】12:线程基础

定义

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,同一进程中的多条线程将共享该进程中的全部系统资源。线程是独立调度和分派的基本单位。 – 百度百科

创建一个线程

继承Thread类

Thread类代表一个线程实例,启动线程的唯一方法是通过Thread类是start()实例方法[注1],start()方法是一个native方法,它将启动一个新的线程,并执行run()方法:

public class CreateThread {

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start(); // 调用start()启动一个线程。  注意不是run()方法
    }

}

class MyThread extends Thread { // 定义自己的线程实现:
    @Override
    public void run() { // run方法内为线程的逻辑
        System.out.println("extends Thread运行");
    }
}

实现Runnable接口

Thread类似实质上是Runnable接口的一个实现类,因为Java的单继承特性,如果一个类已经extends了其他类,就不能再通过extends Thread来定义一个线程,此时可以通过实现Runnable接口来定义一个线程,但是线程依然需要Thread的start()方法启动:

public class CreateThread {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread rt = new Thread(mr); // 将实现了Runnable的类作为Thread的构造参数
        rt.start();// 调用start()启动一个线程
    }
}

class MyRunnable implements Runnable { // 定义自己的线程类并实现run方法
    @Override
    public void run() {
        System.out.println("implements Runnable运行");
    }
}

注1: 线程的启动时调用Thread的start()方法而非run()方法。Thread的start()方法启动一个线程,这个线程处于就绪状态,并在获得CPU之后运行执行run()方法。run()方法被称为线程体,即线程实际的运行逻辑,如果直接调用run()方法,那么就仅仅是一次普通的方法调用,而非是启动一个线程。

线程的生命周期

当线程被创建之后,并不是立即就进入执行状态,进入执行状态后也不一定能一直霸占着CPU一直执行到死亡。线程的生命周期包含创建,就绪,运行,阻塞,死亡五个阶段。
新建: 当一个Thread实例被new出来时,一个线程就处于创建状态了,此时它和一个普通的JVM对象一样存在于内存中。
就绪: 当程序调用了Thread实例的start()方法之后,线程就由创建状态进入到就绪状态,此时的线程等待操作系统分配CPU的执行时间给它。
运行: 一旦线程获得CPU,那么就会进入运行状态,此时线程会执行run()方法内的逻辑:线程体。
阻塞: 当线程因为某些原因需要暂时停止运行放弃CPU时就进入了阻塞状态。进入阻塞状态可能是因为线程调用了wait()方法,或者是线程获取同步锁时锁已经被其他线程占用。
死亡: 线程因为某些原因结束运行,就进入了死亡状态。造成死亡状态的原因可能有:正常运行结束,运行中出现未捕获的异常并抛到线程体外,调用了线程的stop()方法强制中断线程。
在这里插入图片描述

Thread的基本方法

  1. yield():静态方法,执行yield方法时当前线程会让出CPU的执行时间片,由所有线程重新竞争CPU。
  2. interrupt():实例方法,其作用是将实例线程的中断位设置成true,并不是直接中断实例线程。
  3. interrupted():静态方法,其作用是返回当前线程的中断位,并重置当前线程的中断位为false
  4. isInterrupted():实例方法,其作用是返回实例线程的中断位,但并不会重置中断位。
  5. isAlive():实例方法,作用是返回实例线程是否存活。
  6. activeCount():静态方法,作用是返回当前程序中活跃的线程数目。
  7. enumerate(Thread tarray[]):静态方法,作用是将当前程序中所有的活跃线程复制到指定数组中。
  8. currentThread():静态方法,作用是获得当前线程。
  9. isDaemon():实例方法,作用是返回实例线程是否是一个守护线程[注2]
    setDaemon(boolean on):实例方法,作用是指定实例线程是否作为一个守护线程。
  10. setName(String name):实例方法,对实例线程进行命名。
    getName():实例方法,获得实例线程的名称。
  11. setPriority(int newPriority):实例方法,设置实例线程的优先级[注3]
    getPriority():实例方法,获得实例线程的优先级。
  12. sleep(long millis):静态方法,线程睡眠一段时间,不会释放锁。sleep和wait的区别

注2: 见下文
注3: 优先级高的线程并不是一定比优先级低的线程先获得CPU,只是获得CPU的概率大一些

线程上下文切换

上下文切换巧妙的利用了时间片轮转的方式,CPU给每个任务都服务一段时间,然后把当前任务的状态保存下来,在加载下一个任务的状态后,继续服务下一个任务。任务的状态保存以及再加载,这段过程就叫做上下文切换。时间片轮转方式使得多个任务可以在同一颗CPU上执行。
上下文就是指某一个时间点CPU寄存器和程序计数器的内容。
上下文切换的活动:

  1. 挂起一个任务,将任务在CPU中的状态(上下文)存储到内存中。
  2. 在内存中检索下一个任务的上下文并将其在CPU寄存器中恢复。
  3. 跳转到程序计数器指定的位置,恢复该任务的执行。

引起上下文切换的原因:

  • 当前任务的时间片使用完后,系统CPU正常调度下一个任务
  • 当前任务执行到IO阻塞,调度器将当前任务挂起,继续下一个任务
  • 多个任务抢占锁资源,当前任务没有抢到锁资源,调度器将当前任务挂起,继续下一个任务
  • 代码主动挂起当前任务,放弃CPU
  • 硬件中断

守护线程

  • 守护线程,也称为服务线程或后台线程,守护线程即为用户线程提供服务的线程
  • 我们创建的线程默认为用户线程。但是可以通过setDaemon(true)将其设置为守护线程
  • 守护线程中创建的线程也是守护线程
  • 守护线程拥有较低的优先级
  • 守护线程是JVM级别的,并不会随着某个程序的停止而结束。它独立于程序并且周期性的执行某种任务。只有当JVM上所有的用户线程全部停止,守护线程才会停止,之后JVM也就退出了
  • GC线程就是一个经典的守护线程。它周期性的在低优先级进行垃圾回收的工作,当所有用户线程全部停止时不会有新垃圾产生,此时GC线程是JVM上仅剩的线程,GC线程就会自动离开

PS:
【JAVA核心知识】系列导航 [持续更新中…]
上篇导航:11: 从源码看ConcurrentHashMap:一文看懂ConcurrentHashMap
下篇导航:13:一文看懂JAVA线程池,轻松应对面试
欢迎关注…

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yue_hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值