java并发系列(1)——基本概念与Thread API

本文介绍了Java并发编程的基础知识,详细讲解了Thread API的使用,包括线程的创建、启动、终止、线程状态及其转换,还讨论了线程中断、优先级、守护线程以及线程ID和名称等概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 基本概念

进程:计算机分配资源(cpu,内存,磁盘io)的最小单位。进程间相互独立。

线程:cpu 调度,或者说执行任务的最小单位。线程必须依赖进程存在。

线程生命周期

  • new:新建线程;
  • ready/runnable:线程已就绪,等待线程调度器的调度、分配 cpu 资源来执行任务;
  • running:线程已获得 cpu 资源,正在执行任务;
  • blocked:线程阻塞,不占用 cpu,但也不同于就绪状态;
  • terminated:线程终止。

线程上下文:用于保证线程再次获得处理器使用权时,能知道上次执行到哪。一般包括:通用寄存器和程序计数器的内容。

线程上下文切换:一个线程被剥夺处理器使用权,另一个线程被线程调度器选中获得处理器使用权的过程。

线程上下文切换分类

  • 自发性切换:线程自己让出处理器使用权;
  • 非自发性切换:线程被迫交出处理器使用权。

线程上下文切换开销

  • 操作系统保存和恢复上下文,主要是时间开销;
  • 线程调度器进行线程调度,如按照规则计算出由哪个线程获得处理器使用权;
  • 处理器高速缓存重新加载,线程在另一个未执行过此线程的处理器上恢复执行,处理器需要从内存或其它处理器加载数据;
  • 可能会冲刷一级高速缓存的内容到二级缓存或内存。

一些线程调度算法(主要考量:吞吐量、平均响应时间、公平性、切换开销):

  • First In First Out(FIFO,先进先出):先进入队列的先执行,执行完成退出;
  • Shortest Job First(SJF,最短耗时任务优先):耗时短的任务先执行;
  • Round Robin(RR,时间片轮转):每个任务轮流执行相同的时间;
  • Max-Min Fairness(最大最小公平算法);
  • Multi-level Feedback Queue(MFQ)

串行:多个任务依次执行,单核单线程;

并发:多个任务交替执行,单核多线程或多核多线程;

并行:多个任务同时执行,多核多线程,且这些线程分布在不同的核心。

并发编程的好处

  • 充分利用 cpu 资源;
  • 缩短响应时间;

并发编程需要注意

  • 线程安全;
  • 死锁;
  • 线程太多导致死机。

2 java 并发编程基础

2.1 java Thread 基本 API

2.1.1 创建线程
2.1.1.1 new Thread()
public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ": my thread");       
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread();
    }    
}
2.1.1.2 new Thread(new Runnable())
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ": my thread");
            }
        });
    }
2.1.1.3 new Thread(new FutureTask<>(new Callable<>()))
    public static void main(String[] args) {
        Thread thread = new Thread(new FutureTask<>(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                System.out.println(Thread.currentThread().getName() + ": my thread");
                return null;
            }
        }));
    }

java 创建线程 API 仅仅是在内存中创建出 Thread 实例,操作系统中并无线程。

2.1.2 启动线程
        thread.start();

调用 JNI,最终调用操作系统函数创建线程,进入Ready状态,等待调度。

注意:只能调用一次,第二次会报错。

2.1.3 终止线程
  • run 方法执行完成;
  • run 方法执行抛出异常;
  • 调用 stop 方法。

需要注意的是,stop 方法已被弃用了。因为 stop 方法终止线程过于暴力,不管被 stop 的线程在干什么,一旦被 stop 它就得立刻停止执行,然后释放锁,这是非常不安全的。

详情参看《让Thread#stop方法无法终止你的线程》。

2.1.4 线程运行

线程运行过程中包含两个状态:Ready 和 Running。

线程从 Ready 状态到 Running 状态是操作系统调度的,线程自己是控制不了的。

线程从 Running 状态到 Ready 状态,有三种途径:

  • 操作系统调度,收回当前线程的 cpu 资源,分配给其它线程;
  • 调 Thread#yield 方法;
  • 调 sleep(0);
2.1.4.1 yield 方法
    /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();

这是 java 8 的 yield 方法定义,这时候已经是一个 native 方法了。

在某些书上看到说,java 5 之前,yield 方法的实现是 sleep(0),由于没有找到 1.4 的 jdk,所以没有去看。

yield 方法是一个非强制性的提示,告诉线程调度器自己可以让出 cpu,但最终是否会让出 cpu 还要看线程调度器自己的意思。

如果线程调度器响应了 yield 方法,线程将会从 Running 状态退回到 Ready 状态等待调度。

2.1.4.2 sleep(0)

sleep(0) 可以达到与 yield 方法类似的效果。

sleep 方法可以使线程进入 Blocked 状态指定的时间后回到 Ready 状态,而如果将时间设为 0,则相当于直接回到 Ready 状态。

2.1.4.3 注意点

yield 和 sleep(0) 都不靠谱,不要过于相信它们。因为实际效果完全依赖于运行环境和 jvm 实现。

2.1.5 线程阻塞

线程状态从 Running 到 Blocked。

主要有以下几种途径:

  • sleep:进入阻塞状态,持续一定的时间,或被打断;
  • wait:进入阻塞状态,持续一定的时间,或永久直到被唤醒或被打断;
  • join:进入阻塞状态,持续一定时间,或被 join 的线程执行完,或被打断;
  • suspend:进入阻塞状态,永久,直到被 resume。

suspend 也已经被弃用,因为它会永久地带着锁处于阻塞状态,除非被 resume,所以容易导致死锁。

Blocked 状态的线程不参与线程调度,但可以被 stop。

2.1.6 线程唤醒

线程状态从 Blocked 到 Ready。

对应于线程阻塞的几种途径:

  • sleep:指定时间到,或被打断;
  • wait:指定时间到,或被 notify,或被打断;
  • join:指定时间到,或被 join 的线程执行完,或被打断;
  • suspend:被 resume。

resume 方法唯一的作用就是唤醒被 suspend 的线程。由于 suspend 被弃用,所以 resume 也被弃用。

2.1.7 线程打断

即 interrupt,主要涉及三个方法:

  • void interrupt();
  • boolean isInterrupted();
  • static boolean interrupted()。
2.1.7.1 void interrupt()
    public void interrupt() {
        //...
    }

作用是:将调用此方法的线程置为被打断状态。可以假想存在一个 isInterrupted 标识位,并且被置为了 true,当然 Thread 类里面并没有这个成员变量。

主要应用场景有:

  • 借这个标识位做一些业务逻辑的判断;
  • 打断 sleep 方法,使其抛出 InterruptedException;
  • 打断 wait 方法,使其抛出 InterruptedException;
  • 打断 join 方法,使其抛出 InterruptedException。

sleep,wait,join 方法被打断后,会自动清除打断状态。

2.1.7.2 boolean isInterrupted()
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

作用是:检测线程是否为被打断状态。是则返回 true,否则返回 false。

简单来说,调用了 interrupt() 方法,那么再调用 isInterrupted() 方法就会返回 true。

2.1.7.3 static boolean interrupted()
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

作用是:检测线程是否为被打断状态。是则返回 true,否则返回 false。同时,清除打断状态。

与 isInterrupted() 方法的区别是:

  • 如果线程调用了 interrupt() 方法,那么 isInterrupted() 方法无论调多少次返回都是 true;
  • 如果线程调用了 interrupt() 方法,第一次调用 interrupted() 方法返回 true,同时清除标识位,后面再调用 interrupted() 或者 isInterrupted() 方法都会返回 false,直到再次调用 interrupt() 方法。
2.1.8 线程优先级
    public final void setPriority(int newPriority) {
        //...
    }

java 定义了 10 个不同的优先级,从低到高依次为 1~10,但可靠性不高,要依赖操作系统。

一般情况都不要设置。

2.1.9 守护线程
    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

通过 setDaemon(true) 可将线程设为守护线程。

当只有守护线程在运行(即所有用户线程退出)时,jvm 进程自动结束。

需要注意的是:

  • setDaemon() 方法必须在 start() 方法之前调用,否则会抛异常;
  • 守护线程的自动终止比 stop() 方法更暴力,它是因为 jvm 进程退出而终止,所以 finally 代码块也不能保证被执行。
2.1.10 线程 id,线程名称

线程 id 是在同一次 jvm 唯一的,不可设置。

线程名称可设置,并且可以设置相同,虽然并不建议。

2.2 线程生命周期

在这里插入图片描述

下一篇:《java并发系列(2)——线程共享,synchronized与volatile

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值