好久没写笔记了,重新回归Java,打好基础。
Java 进程中每一个线程都对应着一个 Thread
实例,其中保存着线程的描述信息。
Thread
类
Java 使用 Thread
类表示线程,首先看一个简单的示例。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread();
System.out.println("线程名称: " + thread.getName());
System.out.println("线程ID: " + thread.getId());
System.out.println("线程状态: " + thread.getState());
System.out.println("线程优先级: " + thread.getPriority());
thread.start();
}
运行示例,可以看到如下输出:
thread1.pngThread
类中比较重要的属性包括:
-
tid
,线程ID,通过getId()
方法可以获取线程ID,线程 ID 在进程中是唯一的。 -
name
,线程名称,通过getName()
方法可以获取线程名称,通过setName(String name)
方法可以设置线程名称。 -
priority
, 线程优先级,通过getPriority()
方法可以获取线程优先级,通过setPriority(int priority)
方法可以设置线程优先级。Java线程最小值为1,最大值为10,默认为5。 -
daemon
,标记线程是否为守护线程,setDaemon(boolean on)
方法可以设置线程是否为守护线程。默认值为false
,表示为普通的用户线程,不是守护线程。 -
threadState
,线程状态,以整数形式表示,通过getState()
方法可以返回当前线程的状态。返回值为如下枚举:
public static enum State {
NEW, // 新建
RUNNABLE, // 就绪、运行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 休时等得
TERMINATED; // 结束
}
Thread
类中还有几个常用的方法:
-
start()
方法,用来启动一个线程。 -
run()
方法,作为线程代码逻辑的入口方法。当调用start()
方法启动一个线程后,只要线程获取了 CPU 执行时间,就会进入run()
方法执行用户代码。
另外,通过静态方法 currentThread()
可以获取当前线程的实例对象。
通过继承 Thread
类创建线程
通过继承 Thread
类创建线程包含两个步骤:
- 编写一个继承
Thread
类的新线程类。 - 重写
run()
方法,将要执行的业务代码添加到其中。
下面看一个简单的示例:
public class createThread1 {
static int threadNo = 1;
static class MyThread extends Thread {
public MyThread() {
super("MyThread-" + threadNo);
threadNo++;
}
public void run() {
for(int i = 0; i< 3; i++) {
System.out.println(getName() + ",轮次: " + i);
}
System.out.println(getName() + "运行结束.");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
thread = new MyThread();
thread.start();
}
}
}
运行代码,可以得到输出:
thread2.png通过实现 Runnable
接口创建线程
如果看下 Thread
类的源码,我们会发现它实现了一个接口 Runnable
。Runnable
源码如下,其中只包含一个抽象方法 run()
。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Thread
类中包含着一个拥有 Runnable
参数的构造器,利用这个构造器我们就可以生成 Thread
类。
public Thread(Runnable target)
public Thread(Runnable target, String name)
我们可以通过实现 Runnable
接口来创建一个线程。具体步骤如下:
- 定义一个实现了
Runnable
接口的类。 - 实现
Runnable
接口的run()
方法,将代码逻辑放入其中。 - 通过
Thread
类创建线程对象,将现了Runnable
接口的类实例传入其中。
看一个例子:
public class createThread2 {
static int threadNo = 1;
static class RunTarget implements Runnable {
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ", 轮次: " + i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
Runnable target = new RunTarget();
thread = new Thread(target, "MyRunnableThread-" + threadNo);
threadNo++;
thread.start();
}
}
}
运行代码,输出如下:
thread3.png我们还可以利用匿名类和 lambda 表达式,更加优雅的使用 Runnbale
接口创建线程。
将上面的例子改造成匿名类:
public class createThread2 {
static int threadNo = 1;
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
thread = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 3; j++) {
System.out.println(Thread.currentThread().getName() + ", 轮次:" + j);
}
}
}, "MyRunnableThread-" + threadNo);
threadNo++;
thread.start();
}
}
}
将上面的例子改造成 Lambda 表达式:
public class createThread2 {
static int threadNo = 1;
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
thread = new Thread(() -> {
for(int j = 0; j < 3; j++) {
System.out.println(Thread.currentThread().getName() + ", 轮次:" + j);
}
}, "MyRunnableThread-" + threadNo);
threadNo++;
thread.start();
}
}
}
使用 Runnable
接口创建现成的方式优点是可以更好地分离逻辑和数据,多个线程能够并行处理同一个资源。
通过 Callable
和 FutureTask
创建线程
通过继承 Thread
类或者实现 Runnable
接口创建线程的缺陷是无法获取异步结果。
如果要使用异步执行,那么就要用 Callable
接口和 FutureTask
类结合创建线程。
Callable
接口
Callable
接口源码如下,它是一个泛型接口,也是一个函数式接口。其唯一的抽象方法 call()
有返回值,该方法还有一个 Exception
的异常声明,允许方法的实现版本的内部异常直接抛出,并且允许不予捕获。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
RunnableFuture
接口
RunnableFuture
接口的源码如下,它也是一个泛型接口,并且继承了 Runnable
和 Future
接口,这样它一方面可以作为 Thread
线程实例的 target
实例,另一方面可以异步执行。
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
下面看一下为什么继承了 Future
接口就可以异步执行了。
Future
接口
Future
接口是用来处理异步任务的,它主要提供了三大功能:
- 能够取消异步执行中的任务。
- 判断异步任务是否执行完成。
- 获取异步任务完成后的执行结果。
public interface Future<V> {
boolean cancel(boolean mayInterruptRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
各个方法说明:
-
V get()
:获取异步任务执行的结果。注意,这个方法的调用是阻塞性的,如果异步任务没有执行完成,异步结果获取线程会一直阻塞,直到异步任务执行完成,将结果返回给调用线程。 -
V get(long timeout, TimeUnit unit)
:设置时限并获取异步任务执行的结果,如果阻塞时间超过设定的时限,该方法就抛出异常。 -
boolean isDone()
:获取异步任务是否执行完成,执行完成则返回true
。 -
boolean isCancelled()
:获取异步任务是否取消,任务完成前取消则返回true
。 -
boolean cancel(boolean mayInterruptRunning)
:取消异步任务的执行。
FutureTask
类
Future
只是个接口,而 FutureTask
类是它的一个默认实现,更准确的说, FutureTask
类实现了 RunnableTask
接口。
FutureTask
类中有一个 Callable
类型的成员,用来保存任务。FutureTask
的 run()
方法会执行 call()
方法。
创建线程
步骤如下:
- 创建一个
Callable
接口的实现类,并实现其call()
方法,在其中编写异步执行逻辑,可以有返回值。 - 使用
Callable
实现类的实例构造一个FutureTask
实例。 - 使用
FutureTask
实例作为Thread
构造器target
的入参,构造Thread
线程实例。
这样线程就构造好了,线程启动后,我们还可以调用 FutureTask
对象的 get()
方法阻塞性地获得并发线程的执行结果。
public class createThread3 {
static class MyTask implements Callable<Long> {
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " 线程开始运行.");
Thread.sleep(500);
for(int i = 0; i < 100000000; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis() - startTime;
System.out.println(Thread.currentThread().getName() + " 线程结束运行.");
return used;
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
FutureTask<Long> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask, "MyThread");
thread.start();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 先歇一会.");
for(int i = 0; i < 100000000 / 2; i++) {
int j = i * 10000;
}
System.out.println(Thread.currentThread().getName() + " 获取并发任务的执行结果.");
try {
System.out.println(thread.getName() + " 线程占用时间: " + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 运行结束.");
}
}
通过 FutureTask
的 get
方法获取结果,有 2 种情况:
- 异步任务执行完成,直接返回结果。
- 异步任务没有执行完成,一直阻塞到执行完成返回结果。
通过线程池创建线程
前面创建的线程在使用完之后就销毁了,比较浪费时间和资源,为了不频繁创建和销毁线程,需要对线程进行复用,这时候就要用到线程池技术。
Java 种提供了一个静态工厂 Executors
类来创建不同的线程池。简单示例如下:
private static ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService
是 Java 提供的线程池接口,可以通过它的实例提交或者执行任务。ExecutorService
实例负责对池中的线程进行管理和调度,并且可以控制最大并发线程数,同时提供了定时执行、定频执行、单线程、并发数控制等功能。
向 ExecutorService
线程池提交异步执行 target
目标任务的常用方法:
// 执行一个 Runnable 类型的目标实例,无返回
void execute(Runnable command);
// 提交一个 Callable 类型的目标实例,返回一个 Future 异步任务实例
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 类型的目标实例,返回一个 Future 异步任务实例
Future<?> submit(Runnable task);
下面看一个实战例子。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class createThread4 {
private static ExecutorService pool = Executors.newFixedThreadPool(3);
static class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ", 轮次: " + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class MyTask implements Callable<Long> {
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " 线程开始运行.");
Thread.sleep(500);
for(int i = 0; i < 100000000; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis() - startTime;
System.out.println(Thread.currentThread().getName() + " 线程结束运行.");
return used;
}
}
public static void main(String[] args) throws Exception{
pool.execute(new MyThread());
pool.execute(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName() + ", 轮次: " + j);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Future future = pool.submit(new MyTask());
Long result = (Long)future.get();
System.out.println("异步任务的执行结果为: " + result);
}
}
运行结果如下:
thread4.png可以看到线程池中的线程默认名称和普通线程也有所不同。
注意其中 ExecutorService
线程池的 execute
和 submit
方法有如下区别:
-
接收参数不一样。
submit
方法可以接收无返回值的Runnable
类型和有返回值的Callable
类型,execute
仅接收无返回值的Runnable
类型或者Thread
实例。 -
submit
方法有返回值,而execute
方法没有。
网友评论