源码学习day3—线程

139 阅读5分钟

线程和进程的区别

进程是一个独立的运行环境,而线程是进程中执行的一个任务

他们的本质区别是:是否单独占用内存地址空间及其他系统资源

进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位

线程的实现

Java中,JDK提供了Thread类和Runnable接口来让我们实现自己的线程类

  • 继承Thread类,并重写run方法
  • 实现Runnable接口的run方法
继承Thread类
public class Demo {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.start();
    }
}

我们在程序中调用start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法

注意:start()方法不可重复调用,重复调用会抛出java.lang.IllegalStateException异常

实现Runnable接口
public class Demo {
    public static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {

        new Thread(new MyThread()).start();

        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
            System.out.println("Java 8 匿名内部类");
        }).start();
    }
}

Thread类的几个常用方法

  • currentThread():静态方法,返回对当前正在执行的线程对象的引用
  • start():开始执行线程的方法,java虚拟机会调用线程内的run()方法
  • yield():当前线程愿意让出对当前处理器的占用。这里,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的
  • sleep():静态方法,使当前线程睡眠一段时间
  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的

面试题

线程的状态有哪些,它是如何工作的

线程的状态以枚举的方式被定义在Thread的源码中,有6个状态:

// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
  • NEW:新建未启动,只是创建了线程而并没有调用start()方法,此时线程处于NEW状态

  • RUNNABLE:表示当前线程正在运行中。处于RUNNABLE状态的线程在Java虚拟机中运行,也有可能在等待CPU分配资源

  • BLOCKED,阻塞等待锁的线程状态,表示处于阻塞状态的线程正在等待监视器锁,比如等待执行 synchronized 代码块或者使用 synchronized 标记的方法

  • WAITING,等待状态,一个处于等待状态的线程正在等待另一个线程执行某个特定的动作,唤醒:Object.notify() 或 Object.notifyAll()

    ​ 调用如下三个方法会使线程进入等待状态:

    • Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
    • Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法;
    • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度
  • TIMED_WAITING,计时等待状态,和等待状态(WAITING)类似,它只是多了超时时间,比如调用了有超时时间设置的方法 Object.wait(long timeout) 和 Thread.join(long timeout) 等这些方法时,它才会进入此状态

  • TERMINATED,终止状态,表示线程已经执行完成

**线程的工作模式:**首先先要创建线程并指定线程需要执行的业务方法,然后再调用线程的 start() 方法,此时线程就从 NEW(新建)状态变成了 RUNNABLE(就绪)状态,此时线程会判断要执行的方法中有没有 synchronized 同步代码块,如果有并且其他线程也在使用此锁,那么线程就会变为 BLOCKED(阻塞等待)状态,当其他线程使用完此锁之后,线程会继续执行剩余的方法

当遇到 Object.wait() 或 Thread.join() 方法时,线程会变为 WAITING(等待状态)状态,如果是带了超时时间的等待方法,那么线程会进入 TIMED_WAITING(计时等待)状态,当有其他线程执行了 notify() 或 notifyAll() 方法之后,线程被唤醒继续执行剩余的业务方法,直到方法执行完成

BLOCKED和WAITING的区别

BLOCKED可以理解为线程还处于活跃状态,只是在阻塞等待其他线程使用完某个锁资源

WAITING主要是自身调用了Object.wait() 或着是 Thread.join() 又或者是 LockSupport.park() 而进入等待状态,只能扽带其他线程执行某个特定动作才可被唤醒

Start()和run()的区别

Start()源码

public synchronized void start() {
    // 状态验证,不等于 NEW 的状态会抛出异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    // 通知线程组,此线程即将启动

    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            // 不处理任何异常,如果 start0 抛出异常,则它将被传递到调用堆栈上
        }
    }
}

run()源码

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

start() 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全

run() 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run() 方法其实就是此线程要执行的业务方法

从执行的效果来说,start() 方法可以开启多线程,让线程从 NEW 状态转换成 RUNNABLE 状态,而 run() 方法只是一个普通的方法。

start() 方法不能被多次调用,否则会抛出 java.lang.IllegalStateException;而 run() 方法可以进行多次调用,因为它只是一个普通的方法而已

线程优先级有什么用

// 线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;

// 线程默认优先级
public final static int NORM_PRIORITY = 5;

// 线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10

线程的优先级为抢占式,即线程抢占CPU时间片的概率,优先级越高的线程优先执行的概率就越大,注意是有一定可能优先执行,并不是必然

程序中可通过方法:Thread.setPriority(int newPriority)来设置优先级