Thread学习笔记

36 阅读6分钟

虽然一个进程有很多个线程,但是在一个CPU内核上,同一时刻只能有一个线程是正在执行的,该线程也被叫作当前线程

Thread 类

属性:private int priority,保存一个Thread线程实例的优先级
Java线程优先级的最大值为10,最小值为1,默认值为5。

是否为守护线程
属性:private boolean daemon = false,该属性保存Thread线程实例的守护状态,默认为false, 表示是普通的用户线程,而不是守护线程。

线程的状态
属性:private int threadStatus,该属性以整数的形式保存线程的状态。
方法:public Thread.State getState(),返回表示当前线程的执行状态,为新建、就绪、运行、 阻塞、结束等状态中的一种。

NEW, //新建
RUNNABLE, //就绪、运行
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //计时等待
TERMINATED; //结束

在Java线程的状态中,就绪状态和运行状态在内部都用同一种状态RUNNABLE表示。就绪状 态表示线程具备运行条件,正在等待获取CPU时间片;运行状态表示线程已经获取了CPU时间片, CPU正在执行线程代码逻辑。

取得当前线程
方法:public static Thread currentThread() 用于获取当前线程的Thread实例对象

创建线程的方式

继承thread类

public class TestThread extends Thread {

    @Override
    public void run() {
        System.out.println("进入线程");
        try {
            sleep(10000);
            System.out.println("当前线性:" +Thread.currentThread());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread thread = new TestThread();
        thrad.start();
}

实现runnable接口

Thread thread = new Thread(()->{
    System.out.println("进入Runnable");
    // 休眠10000
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
thread.start();

使用 Callable 和 FutureTask 创建线程

Callable 接口

Callable接口的call()有返回值

RunnableFuture 接口

该接口与Runnable接口、Thread类紧密相关。
RunnableFuture接口实现了两个目标:一是可以作为Thread线程实例的target实例,二是可以获取异步执行的结果。

Future 接口

Future接口至少提供了三大功能:

1)能够取消异步执行中的任务。 2)判断异步任务是否执行完成。 3)获取异步任务完成后的执行结果。

对Future接口的主要方法详细说明如下:

V get():获取异步任务执行的结果。注意,这个方法的调用是阻塞性的。如果异步任务没
有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到异步任务执行完
成,其异步结果返回给调用线程。

V get(Long timeout , TimeUnit unit):设置时限,(调用线程)阻塞性地获取异步任务执行
的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一个阻塞时长
限制,不会无限制地阻塞和等待,如果其阻塞时间超过设定的timeout时间,该方法将抛出
异常,调用线程可捕获此异常。

boolean isDone():获取异步任务的执行状态。如果任务执行结束,就返回trueboolean isCancelled():获取异步任务的取消状态。如果任务完成前被取消,就返回trueboolean cancel(boolean mayInterruptRunning):取消异步任务的执行。

FutureTask 类

FutureTask类是Future接口的实现类,提供了对异步任务的操作的具体实现。

image.png

FutureTask的outcome实例属性用于保存callable成员call()方法的异步执行结果。在FutureTask 类run()方法完成callable成员的call()方法的执行之后,其结果将被保存在outcome实例属性中,供 FutureTask类的get()方法获取。


Callable<Long> callable = ()-> {
    System.out.println("Callable");
    return 23123L;
};
FutureTask<Long> futureTask = new FutureTask<Long>(callable);
Thread thread1 = new Thread(futureTask);
thread1.start();
System.out.println("获取异步调用结果  " +  futureTask.get());

通过线程池创建线程

线程的核心原理

线程的调度与时间片

由于CPU的计算频率非常高,每秒计算数十亿次,因此可以将CPU的时间从毫秒的维度进行分段,每一小段叫作一个CPU时间片。

线程只有得到CPU时间片才能执行指令,处于执行状态,没有得到时间片的线程处于就绪状态,等待系统分配下一个CPU时间片。由于时间片非常短,在各个线程之间快速地切换,因此表现出来的特征是很多个线程在“同时执行”或者“并发执行

分时调度模型和抢占式调度模型

1)分时调度模型:系统平均分配CPU的时间片,所有线程轮流占用CPU。分时调度模型在时间片调度的分配上,所有线程“人人平等”。

2)抢占式调度模型:系统按照线程优先级分配CPU时间片。优先级高的线程,优先分配CPU时间片,如果所有就绪线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。

java的调度方式

Java的线程管理和调度是委托给操作系统完成的,与之相对应,Java的线程调度也是使用抢占式调度模型,因此Java的线程都有优先级。

线程状态

NEW 状态
通过new Thread(...)已经创建线程,但尚未调用start()启动线程,该线程处于NEW(新建)状态。

RUNNABLE 状态
Java把Ready(就绪)和Running(执行)两种状态合并为一种状态:RUNNABLE(可执行)状态(或者可运行状态)。调用了线程的start()实例方法后,线程就处于就绪状态。此线程获取到CPU时间片后,开始执行run()方法中的业务代码,线程处于执行状态。

BLOCKED 状态 处于BLOCKED(阻塞)状态的线程并不会占用CPU资源,以下情况会让线程进入阻塞状态:

1)线程等待获取锁
等待获取一个锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态

2)IO阻塞
线程发起了一个阻塞式IO操作后,如果不具备IO操作的条件,线程就会进入阻塞状态。IO包括磁盘IO、网络IO等。IO阻塞的一个简单例子:线程等待用户输入内容后继续执行

WAITING 状态
处于WAITING(无限期等待)状态的线程不会被分配CPU时间片,需要被其他线程显式地唤醒,才会进入就绪状态。线程调用以下3种方法让自己进入无限等待状态:

Object.wait()方法,对应的唤醒方式为:Object.notify()/Object.notifyAll()。

Thread.join()方法,对应的唤醒方式为:被合入的线程执行完毕。  

LockSupport.park()方法,对应的唤醒方式为:LockSupport.unpark(Thread)。

TIMED_WAITING 状态
处于TIMED_WAITING(限时等待)状态的线程不会被分配CPU时间片,如果指定时间之内没有被唤醒,限时等待的线程会被系统自动唤醒,进入就绪状态。以下3种方法会让线程进入限时等待状态:

TERMINATED 状态
线程结束任务之后,将会正常进入TERMINATED(死亡)状态;或者说在线程执行过程中发生了异常(而没有被处理),也会导致线程进入死亡状态