1.Thread 类详解
Thread类是Java语言中重要的基础类,位于java.lang包中。Thread类有不少非常重要的属性和方法,用于存储和操作线程的描述信息。
1.1 线程ID
属性:private long tid,此属性用于保存线程的 Id
方法:public long getId(),获取线程 Id;线程 Id 由 JVM 进行管理,在进程内唯一。
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 才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中,会为新的线程分配需要的资源。
方法二:public void run(),作为线程代码逻辑的入口方法。run 方法不是由用户程序来调用的,当调用 start 方法启动一个线程之后,只要线程获得了 CPU 执行时间,便进入 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 接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法 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 继承 Runnable 接口,从而保证了其实例可以作为 Thread线程实例的 target 目标;同时,RunnableFuture 通过继承 Future 接口,从而保证了通过它可以获取未来的异步执行结果。
2.3.3 Future 接口
Future 接口提供了三大功能:
1.能够取消异步执行中的任务
2.判断异步任务是否执行完成
3.获取异步任务完成后的执行结果
boolean isDone():获取异步任务的执行状态。如果任务执行结束,返回 true。
boolean isCancelled():获取异步任务的取消状态。如果任务完成前被取消,则返回 true。
boolean cancel(boolean mayInterruptRunning):取消异步任务的执行。
V get():获取异步任务执行的结果。这个方法的调用是阻塞性的。如果异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到到异步任务执行完成,其异步结果返回给调用线程。
V get(Long timeout , TimeUnit unit) :设置时限,(调用线程)阻塞性的获取异步任务执行的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一个阻塞时长限制,不会无限制的阻塞和等待,如果其阻塞时间超过设定的 timeout 时间,该方法将抛出异常,调用线程可捕获此异常。
Future 是一个对异步任务进行交互、操作的接口。但是 Future 仅仅是一个接口,通过它没有办法直接完成对异步任务的操作,JDK 提供了一个默认的实现了——FutureTask 类。
2.3.4 FutureTask 类
从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 执行目标
实例在执行之后,没有办法对其异步执行过程进行控制,只能任其执行,一直到其执行结束。