Thread类详解以及创建线程的四种方法

1.Thread 类详解

Thread类是Java语言中重要的基础类,位于java.lang包中。Thread类有不少非常重要的属性和方法,用于存储和操作线程的描述信息。

1.1 线程ID

属性:private long tid,此属性用于保存线程的 Id


线程ID

方法:public long getId(),获取线程 Id;线程 Id 由 JVM 进行管理,在进程内唯一。


获取线程 ID

1.2 线程名称

属性:private volatile String name,该属性保存一条 Thread 线程实例的名字


线程名称

方法一:获取线程名称


获取线程名称

方法二:public final synchronized void setName(String name),设置线程名称。
设置线程名称

方法三:Thread(String threadName),通过此构造方法,给线程设置一个定制化的名字


给线程设置一个定制化的名字

1.3 线程优先级

属性:private int priority,保存一条 Thread 线程实例的优先级。


线程实例的优先级

Java 线程的优先级最大值为 10,最小值为 1,默认值为 5。这三个优先级值为三个常量值,也是在 Thread 类中使用类常量定义,三个类常量如下:


线程的优先级

1.4 线程的状态

属性:private volatile int threadStatus ,该属性以整数的形式保存线程的状态。


线程的状态

方法:public Thread.State getState(),返回表示当前线程的执行状态,为新建、就绪、运行、
阻塞、结束等状态中的一种。


当前线程的执行状态
public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW, //新建

        /**
         * 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.
         */
        RUNNABLE, //就绪、运行

        /**
         * 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}.
         */
        BLOCKED, //阻塞

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */ 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}
  • *
*/ TIMED_WAITING,//计时等待 /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED;//结束 }

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

1.5 线程的启动和运行

方法一:public synchronized void start(),用来启动一个线程,当调用 start 方法后,JVM 才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中,会为新的线程分配需要的资源。


start 方法

方法二:public void run(),作为线程代码逻辑的入口方法。run 方法不是由用户程序来调用的,当调用 start 方法启动一个线程之后,只要线程获得了 CPU 执行时间,便进入 run 方法体去执行具体的用户线程代码。


run 方法

1.6 取得当前线程

就是当前在 CPU 上执行的线程。在没有其他的途径获取当前线程的实例对象的时候,可以通过 Thread.currentThread()静态方法获取


取得当前线程

2. 创建线程的方法

2.1 线程创建方法一:继承Thread类

继承Thread类创建线程的步骤为:

  1.创建一个类继承Thread类,重写run()方法,将所要完成的任务代码写进run()方法中;
  2.创建Thread类的子类的对象;
  3.调用该对象的start()方法,该start()方法表示先开启线程,然后调用run()方法;

public class CreateDemo1 {
    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //创建一个新线程
        ThreadDemo1 thread1 = new ThreadDemo1();
        //为线程设置名称
        thread1.setName("线程一");
        //开启线程
        thread1.start();
    }
}

class ThreadDemo1 extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }
}

2.2 线程创建方法二:实现Runnable接口

实现Runnable接口创建线程的步骤为:

  1.创建一个类并实现Runnable接口
  2.重写run()方法,将所要完成的任务代码写进run()方法中
  3.创建实现Runnable接口的类的对象,将该对象当做Thread类的构造方法中的参数传进去
  4.使用Thread类的构造方法创建一个对象,并调用start()方法即可运行该线程

2.2.1 实现Runable接口

public class CreateDemo2 {
    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //创建一个新线程
        Thread thread2 = new Thread(new ThreadDemo2());
        //为线程设置名称
        thread2.setName("线程二");
        //开启线程
        thread2.start();
    }
}

class ThreadDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }

}

2.2.2 使用Lambda表达式创建

public class CreateDemo2WithLambda {

    public static final int MAX_TURN = 5;
    static int threadNo = 1;
    public static String getCurThreadName() {
        return Thread.currentThread().getName();
    }

    public static void main(String args[]) throws InterruptedException {
        Thread thread = null;
        //使用 Lambda 表达式形式创建和启动线程
        for (int i = 0; i < 2; i++) {
            //①Lambda 表达式
            thread = new Thread( ()-> {
                for (int j = 1; j < MAX_TURN; j++) {
                    System.out.println((getCurThreadName() + ", 轮次:" + j));
                }
                System.out.println((getCurThreadName() + " 运行结束."));
            }, "RunnableThread" + threadNo++);
            thread.start();
        }
        System.out.println((getCurThreadName() + " 运行结束."));
    }
}

大多数情况下,偏向于用实现 Runnable 接口来实现线程执行目标类,这样能使得代码更加的简洁明了。
异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交,所以,更多的做法是,通过实现 Runnable 接口创建异步执行任务,而不是继承 Thread 去创建异步执行任务。

2.3 线程创建方法三:使用 Callable 和 FutureTask 创建线程

继承Thread类或者实现Runable接口这两种方式来创建线程类都有个共同的缺陷:不能获取异步执行的结果,因为run() 方法不支持返回值。为了解决异步执行的结果问题,所以就有了一种新的多线程创建方法:
通过 Callable 接口和 FutureTask 类相结合创建线程。

实现Callable接口创建线程的步骤为:

   1.创建一个 Callable 接口的实现类,并实现其 call()方法,编写好异步执行的具体逻辑,并且可以有返回值。
   2.使用 Callable 实现类的实例,构造一个 FutureTask 实例。
   3.使用 FutureTask 实例,作为 Thread 构造器的 target 入参,构造新的 Thread 线程实例
   4.调用 Thread 实例的 start 方法启动新线程,启动新线程的 run()方法并发执行。其内部的执行过程为:      
      启动 Thread 实例的 run()方法并发执行后,会执行 FutureTask 实例的 run()方法,最 终会并发执
      Callable 实现类的 call()方法
   5.调用 FutureTask 对象的 get()方法,阻塞性的获得并发线程的执行结果。
public class CreateDemo3 {
    public static void main(String[] args) throws Exception {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //创建FutureTask的对象
        FutureTask task = new FutureTask<>(new ThreadDemo3());
        //创建Thread类的对象
        Thread thread3 = new Thread(task);
        thread3.setName("线程三");
        //开启线程
        thread3.start();
        //获取call()方法的返回值,即线程运行结束后的返回值
        String result = task.get();
        System.out.println(result);

    }
}

class ThreadDemo3 implements Callable {

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        return Thread.currentThread().getName()+":"+"返回的结果";
    }
}

2.3.1 Callable 接口
Callable 接口位于 java.util.concurrent 包中


Callable接口

Callable 接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法 call()有返回值,返回值的类型为 Callable 接口的泛型形参类型。Callable 接口类似于 Runnable。不同的是,Runnable 的唯一抽象方法 run()没有返回值,也没有受检异常的异常声明。比较而言,Callable 接口的 call()有返回值有返回值,并且申明了受检异常,其功能更强大一些。
那么,Callable 实例能否和 Runnable 实例一样,作为 Thread 线程实例的 target 来使用?
答案是不可以。Thread 的 target 属性的类型为 Runnable,而 Callable 接口与 Runnable 接口之间没有任
何的继承关系,那么该如何使用Callable接口去创建线程呢?这里就要使用一个在Callable 接口与 Thread 线程之间起到搭桥作用的接口:RunnableFuture 接口。
2.3.2 RunnableFuture 接口
RunnableFuture接口实现了两个目标:

1. 可以作为 Thread 线程实例的 target 实例
2. 可以获取异步执行的结果
RunnableFuture

通过源码可以看出:RunnableFuture 继承 Runnable 接口,从而保证了其实例可以作为 Thread线程实例的 target 目标;同时,RunnableFuture 通过继承 Future 接口,从而保证了通过它可以获取未来的异步执行结果。
2.3.3 Future 接口
Future 接口提供了三大功能:

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

boolean isDone():获取异步任务的执行状态。如果任务执行结束,返回 true。


isDone()

boolean isCancelled():获取异步任务的取消状态。如果任务完成前被取消,则返回 true。


isCancelled()

boolean cancel(boolean mayInterruptRunning):取消异步任务的执行。
image.png

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


V get()

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

Future 是一个对异步任务进行交互、操作的接口。但是 Future 仅仅是一个接口,通过它没有办法直接完成对异步任务的操作,JDK 提供了一个默认的实现了——FutureTask 类。
2.3.4 FutureTask 类
FutureTask 类的UML图

从FutureTask 类的UML图中可以看出:FutureTask 实现了 RunnableFuture 接口,而RunnableFuture 接口继承了 Runnable 接口和 Future 接口,所以,FutureTask 既能当做一个 Runnable类型的 target 执行目标直接被 Thread 执行,也能作为 Future 异步任务来获取 Callable 的计算结果。

2.4 线程创建方法四:通过线程池创建线程

使用线程池创建线程的步骤:

  1.使用Executors类中的newFixedThreadPool(int num)方法创建一个线程数量为num的线程池
  2.调用线程池中的execute()方法执行由实现Runnable接口创建的线程;调用submit()方法执行由实现Callable接口创建的线程
  3.调用线程池中的shutdown()方法关闭线程池
public class CreateDemo4 {
    public static void main(String[] args) throws Exception {

        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        //通过线程池工厂创建线程数量为2的线程池
        ExecutorService service = Executors.newFixedThreadPool(2);
        //执行线程,execute()适用于实现Runnable接口创建的线程
        service.execute(new ThreadDemo4());
        service.execute(new ThreadDemo6());
        service.execute(new ThreadDemo7());
        //submit()适用于实现Callable接口创建的线程
        Future task = service.submit(new ThreadDemo5());
        //获取call()方法的返回值
        String result = task.get();
        System.out.println(result);
        //关闭线程池
        service.shutdown();
    }
}

/**
 * 实现Runnable接口
 */
class ThreadDemo4 implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }

}

/**
 * 实现Callable接口
 */
class ThreadDemo5 implements Callable {

    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
        return Thread.currentThread().getName()+":"+"返回的结果";
    }

}

/**
 * 实现Runnable接口
 */
class ThreadDemo6 implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }

}

/**
 * 实现Runnable接口
 */
class ThreadDemo7 implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+"输出的结果");
    }

}

ExecutorService 线程池的 execute(...)与 submit(...)两类方法的区别如下:

  1.接收的参数不一样
    Submit()可以接受两种入参:无返回值的 Runnable 类型的 target 执行目标实例,和有返回值Callable 类型的 target 执行目标实例。而 execute()仅仅接受一类:无返回值的 Runnable 类型的 target执行目标实例,或者无返回值的 Thread 实例。
  2.submit()有返回值,而 execute()没有
    submit()方法在提交异步 target 执行目标之后,会返回 Future 异步任务实例,以供对 target 的
    异步执行过程进行控制,比如取消执行、获取结果等。execute()没有任何返回,在 target 执行目标
    实例在执行之后,没有办法对其异步执行过程进行控制,只能任其执行,一直到其执行结束。

你可能感兴趣的:(Thread类详解以及创建线程的四种方法)