Java 线程知识笔记 (一) Thread与Runnable

15 篇文章 1 订阅

前言

说到Java的运行,就离不开进程和线程,这两个概念其实也是所有编程语言的核心。无论是什么语言,归根结底就是在操作CPU,让CPU分配相应的计算资源给应用程序。而线程就是CPU能够调度和分派的基本单元,多个线程组合在一起做一件事情就变成了进程。线程可以说是Java中基础的基础,但是由于其不好理解因此很少会被当作Java的基础去介绍和讲解。这篇博客会对Java中线程的使用和部分方法源码做一个整理,对自己是一个基础巩固,顺带希望能帮助其他人更好的理解线程。更多线程知识内容请点击【Java 多线程和锁知识笔记系列】

Thread类

Thread类是Java线程相关的类中的实现类,多数线程有关的类或者接口都得依赖这个类中提供的方法进行,使用方法如下:

public class Thread1 extends Thread{
    public static void main(String[] args) {
        Thread thread=new Thread();
        thread.start();
    }
    @Override
    public void run() {
        System.out.println("thread1 test");
    }
}

一旦调用start()方法就会开启一个线程,执行run()方法内部的逻辑。

Runnable接口

Runnable接口也是一个线程相关的接口,里面只有一个run()方法。

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

上面说Thread的时候,说到线程被启动了就执行了run()方法的逻辑。那么这两个run()方法有什么联系呢?答案是两个run()方法其实是一样的。因为Thread类也实现了Runnable接口,Thread里面的run()方法就是Runnable里面的run()方法。可以说当继承并重写(override) Thread类中的run()方法的时候,就是在写Runnable.run(),毕竟父类的实现,子类也可以重做。

public class Thread implements Runnable { ...... }

由于Runnable只是一个接口,因此它的使用,必须借助Thread类实现,比如。

public class Runnable1 implements Runnable{
    @Override
    public void run() {
        System.out.println("Runnable1 test");
    }
    public static void main(String[] args) {
        new Thread(new Runnable1()).start();
    }
}

这也就是为什么很多博客里说实现了Runnable接口的类,是一个假的线程类,只是实现了run()方法而已。因为不使用Thread之类的线程类辅助创建线程,那么实现了Runnable接口,也只是实现了Runnable接口而已和线程不沾边。

线程的生命周期

刚才通过介绍Thead类和Runnable接口的例子,可以看出线程是有开始的,又开始就得有结束,也会有阻塞等等一系列的方法去修饰线程,这些加在一起被统称为线程的生命周期。人的生命周期有生老病死,线程的生命周期也有生老病死等等一系列的状态,去描述一个线程从创建到销毁的过程。

生命周期状态

下面是在Thread类中定义的生命周期状态,以下英文来自Thread类注释:

状态名字中文名源码注释翻译和描述
NEW初始化状态A thread that has not yet started is in this state.刚刚被创建new Thread(),但是还没有调用start()方法。
RUNNING运行状态N/A线程获取时间片,正在运行,Runnable的子状态。
READY就绪状态N/A线程已经初始化,调用start(),但是还没有分配CPU时间片运行,Runnable的子状态。
RUNNABLE可运行状态(包括READY和RUNNING)A thread executing in the Java virtual machine is in this state. Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.在Java虚拟机中正在执行的线程就是处于这种状态。但是这个线程可能是正在执行(Running),也能是正在等待操作系统其他资源(比如处理器)的一个线程(Ready)。
BLOCKED阻塞状态Thread state for a thread blocked waiting for a monitor lock. A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling {@link Object#wait() Object.wait}.表示被锁阻塞住的线程。处于这个状态的线程一般是等待一个进入synchronized块/方法的锁,或者在调用Object.wait ()之后重新进入synchronized块/方法。
WAITING等待状态Thread state for a waiting thread. A thread is in the waiting state due to calling one of thefollowing methods: {@link Object#wait() Object.wait} with no timeout {@link #join() Thread.join} with no timeout {@link LockSupport#park() LockSupport.park}线程进入等待状态说明当前线程调用了Object.wait()、Thread.join()、LockSupport.park()方法,并且都没有设置timeout时间。
TIME_WAITING超时等待状态Thread state for a waiting thread with a specified waiting time. A thread is in the timed waiting state due to calling one of the following methods with a specified positive waiting time: {@link #sleep Thread.sleep} {@link Object#wait(long) Object.wait} with timeout {@link #join(long) Thread.join} with timeout {@link LockSupport#parkNanos LockSupport.parkNanos} {@link LockSupport#parkUntil LockSupport.parkUntil}这个和上面的类似,只不过这些调用的方法都有一个明确的等待时间,调用下面这些方法Thread.sleep()、Object.wait(long)、Thread.join(long)、 LockSupport.parkNanos()、LockSupport.parkUntil(),可以转入这个状态
TERMINATED终止状态Thread state for a terminated thread. The thread has completed execution.处于这个状态的线程究竟完成了执行,可以被终止了。

状态交替

既然线程的生命周期有各种状态,那么他们之间转换我们可以用一张图来表示。
在这里插入图片描述

从上面的图里可以看到实例化之后就会转到NEW,然后调用start()方法就转化到了RUNNABLE状态,然后在RUNNINGREADY就是在run()方法里面的逻辑,当CPU分配到时间片的时候,会进入了RUNNING状态。而WAITINGBLOCKEDTIME_WAITING则均为挂起的状态。

线程执行的顺序

从线程的生命周期中可以看到,当线程进入RUNNABLE状态以后,线程的执行基本上是看脸吃饭了。因为CPU并不会按照创建顺序去执行不同的线程,而是一个随机的过程,虽然可以设置优先级,但是优先级也仅仅只是一个参考。即便设置了,相同优先级下哪个线程先执行也是不确定的,比如下面的例子。

public class ThreadOrder{
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            System.out.println("t1");
        });
        Thread t2=new Thread(()->{
            System.out.println("t2");
        });
        Thread t3=new Thread(()->{
            System.out.println("t3");
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

例子中t1、t2、t3的顺序每次都不固定,因为在多线程的情况下,CPU并不会按照他们的调用顺序分配时间分片,其执行的流程图为:
在这里插入图片描述

为了保证这些线程顺序执行,可以使用join()方法,修改程序为下面的样子,再次运行。无论多少次都是按照t1、t2、t3的顺序执行的。

public class ThreadOrder{
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            System.out.println("t1");
        });
        Thread t2=new Thread(()->{
            System.out.println("t2");
        });
        Thread t3=new Thread(()->{
            System.out.println("t3");
        });
        t1.start();  t1.join(); //对每个线程调用join()方法
        t2.start();  t2.join();
        t3.start();  t3.join();
    }
}

为什么加入了join()方法就会导致线程执行有顺序呢?这就需要进入join()的源码里看了。

public final void join() throws InterruptedException {
    join(0);
}

继续进入join(0)方法。

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

首先这个方法被synchronized关键字修饰了,可以看作执行到join()的时候资源是被锁住的。而外部传递的是0这个值,进入到方法里。当millis == 0且当前线程仍然活着的时候isAlive(),就会执行wait(0),这个方法被native修饰是个本地方法,所以这里wait的并不是子线程,而是主线程。

public final native void wait(long timeout) throws InterruptedException;

也就意味着当子线程调用join()方法以后,主线程就会wait(),那么其执行的流程就和修改前的大大的不一样了:每次join()主线程必须让出CPU时间片给子线程,等待子线程执行完毕,才能接着执行。就意味着t1,t2,t3的启动执行被固定了,因此总是有序的执行。修改后流程图如下:
在这里插入图片描述

总结

本篇主要讲了线程Thread类,Runnable接口的使用,线程的生命周期,如何保证线程的顺序,以及join()方法的源码和原理。由于Thread是类,Runnable是接口,一般会推荐使用Runnable接口来实现线程,因为Runnable接口比Thead是有优势的。

  1. 语法上Runnable是接口,就可以避免Java的单继承特性带来的局限性。
  2. 复用性上使用接口实现的类,能够使多个线程共享这部分代码,可以做到代码数据独立,增加程序的健壮性。
  3. 当要是用线程池或者Callable的时候,由于只留有Runnable接口的参数,因此只能使用Runnable,而不能使用继承Thread的类。

但无论是Thread类或者Runnable接口,其run()方法都没有返回值。有些时候希望线程有返回值的时候就需要用到Callable接口,那么下一篇【Java 线程知识笔记 (二) Callable与Future】就会重点讲Callable接口和Future接口的使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值