文章目录
2. Java中的线程
1. Thread类
Java中的线程主要是线程类:Thread类。
以JDK1.8为例,Thread中主要属性内容如下:
public class Thread implements Runnable {
// 线程名称
private volatile String name;
// 线程优先级,最小1,最大10,默认是5
private int priority;
// 是否是守护线程,默认不是
private boolean daemon = false;
// 将要运行什么代码
private Runnable target;
// 线程id
private long tid;
// 线程状态,是数字类型,是状态枚举类的一个转换
private volatile int threadStatus = 0;
// 状态枚举
public enum State {
NEW, // 新建状态
RUNNABLE, // 就绪、运行 就绪:已经可以运行,等待CPU执行 运行:正在被CPU运行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 计时等待
TERMINATED; // 结束
}
}
主要方法内容如下:
// 使线程开始执行,JVM将会执行run方法,将会开启一个新的线程执行用户run方法中的逻辑。start过程中会分配线程需要的资源
public synchronized void start() {
...
}
// 代码逻辑的入口,run方法不是由用户程序调用的,当start方法执行之后,只有线程获得了CPU执行时间,才会执行run方法
public void run() {
if (target != null) {
target.run();
}
}
start方法用于线程启动,run方法是用户代码逻辑的入口。
2.线程的创建方式
1. 继承Thread类
继承Thread类,重写run方法,在run方法中写用户代码逻辑,例如:
public class ExtendThread {
public static void main(String[] args) {
DemoThread demoThread = new DemoThread();
demoThread.start(); // 创建线程对象,并启动线程
}
}
class DemoThread extends Thread {
// 可以有其他的业务逻辑
@Override
public void run() {
// 写用户逻辑,这里输出了一些线程信息
System.out.println("Thread Name:: " + getName());
System.out.println("Thread Id:: " + getId());
System.out.println("Thread State:: " + getState());
System.out.println("Thread Priority:: " + getPriority());
}
}
输出结果如下:
Thread Name:: Thread-0
Thread Id:: 11
Thread State:: RUNNABLE
Thread Priority:: 5
2. 实现Runnable接口
public class ImplementRunnable {
public static void main(String[] args) {
// 借助Thread类启动线程执行
Thread thread = new Thread(new DemoRunnable());
thread.start();
}
}
class DemoRunnable implements Runnable {
@Override
public void run() {
// 写用户逻辑
// 因为不是继承Thread类,所以Thread中的方法不能直接使用,需要先获取对当前线程对象,然后调用对象的方法
Thread currentThread = Thread.currentThread();
System.out.println("Thread Name:: " + currentThread.getName());
System.out.println("Thread Id:: " + currentThread.getId());
System.out.println("Thread State:: " + currentThread.getState());
System.out.println("Thread Priority:: " + currentThread.getPriority());
}
}
执行结果:
Thread Name:: Thread-0
Thread Id:: 11
Thread State:: RUNNABLE
Thread Priority:: 5
还记得前边的Thread类的属性和方法吗?Thread类中的run方法,如果target不为null,就执行target中的run方法。 传入target之后,启动线程的时候,就会转调传入的Runnable对象的run方法
如果run方法中的代码只使用一次,可以考虑使用匿名内部类或Lambda表达式实现Runnable接口,这样写会稍稍简单一些。
使用匿名内部类改写代码,如下所示:
public class InnerClassImplementRunnable {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
System.out.println("Thread Name:: " + currentThread.getName());
System.out.println("Thread Id:: " + currentThread.getId());
System.out.println("Thread State:: " + currentThread.getState());
System.out.println("Thread Priority:: " + currentThread.getPriority());
}
});
thread.start();
}
}
使用Lambda表达式实现如下所示:
public class LambdaImplementRunnable {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
Thread currentThread = Thread.currentThread();
System.out.println("Thread Name:: " + currentThread.getName());
System.out.println("Thread Id:: " + currentThread.getId());
System.out.println("Thread State:: " + currentThread.getState());
System.out.println("Thread Priority:: " + currentThread.getPriority());
});
thread.start();
}
}
继承Thread的方法和实现Runnable接口方法的区别
- 继承Thread类创建的对象直接就是线程对象
实现Runnable接口,并不能直接创建出线程对象,需要把Runnable对象传给Thread类,然后使用Thread类创建线程对象 - 继承Thread类想要访问线程的属性,可以直接调用线程的方法
实现Runnable接口要想访问线程的属性,就需要先获取到当前线程,然后再调用线程对象的方法获取 - 继承Thread类之后不能再继承其他类,因为Java是单继承的
实现Runnable接口并不影响继承或实现其他类 - 实现Runnable接口更容易实现数据和逻辑的分离,使用Runnable更容易访问共享的数据资源
3. 使用Callable和FutureTask创建线程
继承Thread类或实现Runnable接口都不能获取到线程的执行结果,但是很多场景都需要获取异步的执行结果。为了解决这个问题,Java在1.5之后出现了Callable接口和FutureTask相结合创建线程。
Callable接口
package java.util.concurrent;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
可以看到,Callable接口是java.util.concurrent包下的,call方法可以抛出异常,且有返回值,返回值类型为Callable接口的泛型类型。
但是因为Callable接口和Runnable接口以及Thread类没有关联,要想使用Callable接口创建线程,需要借助RunnableFuture接口。
RunnableFuture接口
package java.util.concurrent;
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
RunnableFuture接口也位于java.util.concurrent包下,继承了Runnable接口,保证可以作为Thread类中的target目标。同时继承了Future接口,保证可以获取未来的异步执行结果。
Future接口介绍
public interface Future<V> {
// 取消异步任务的执行
boolean cancel(boolean mayInterruptIfRunning);
// 任务是否被取消。如果在完成之前被取消,则返回true
boolean isCancelled();
// 任务是否已完成,不管是正常完成、被取消、抛出异常终止,都会返回true
boolean isDone();
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 在规定时间内获取任务完成的执行结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
注意:
get()方法是阻塞的,如果异步线程没有完成执行,调用线程会一直被阻塞到异步线程执行完成,然后将结果返回给调用线程
get(long timeout, TimeUnit unit)方法也是阻塞的,但是有一个阻塞时长,如果超过设置的阻塞时间仍没有执行完成,方法会抛出异常,调用线程可以捕获到异常。
总体来说Future是一个与异步线程进行交互、操作的接口。
JDK提供了一个默认的实现类:FutureTask
FutureTask类介绍
FutureTask部分代码如下:
package java.util.concurrent;
public class FutureTask<V> implements RunnableFuture<V> {
// 存放要运行的callable对象,运行一次之后会被清空
private Callable<V> callable;
// 线程运行的结果或抛出的异常将会存放到这个属性,可以通过get方法获取
private Object outcome;
// 接收Callable对象的构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
}
使用Callable接口+FutureTask类创建线程大致需要以下几步:
- 创建Callable对象
- 使用Callable对象构建FutureTask对象
- 使用FutureTask对象创建Thread对象
- 使用Thread对象启动线程
- 使用FutureTask对象获取结果
示例代码:
public class TestFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 使用Callable接口构建FutureTask对象
FutureTask<String> task = new FutureTask<String>(() -> {
long start = System.currentTimeMillis();
String name = Thread.currentThread().getName();
System.out.println(name + ":线程开始执行~");
Thread.sleep(1000);
System.out.println(name + ":线程执行结束~");
long end = System.currentTimeMillis();
return "线程" + name + "执行耗时:" + (end - start) + "毫秒";
});
// 使用FutureTask对象构建线程对象
Thread thread = new Thread(task);
// 启动线程
thread.start();
// 会等待异步线程执行完毕
String result = task.get();
System.out.println("获取到的结果是:" + result);
}
}
执行结果如下:
Thread-0:线程开始执行~
Thread-0:线程执行结束~
获取到的结果是:线程Thread-0执行耗时:1016毫秒
4. 使用线程池创建线程
前边的示例中,所创建的线程在执行完成后就都被销毁了,这些线程都是不可复用的。
但是其实创建一个线程的时间成本、资源耗费都是很高的,在并发场景下,不能进行频繁的线程实例的创建与销毁,而是需要把已经建好的线程实例进行复用,于是就出现了线程池技术。
Java提供了Executors工厂类,可以快捷的创建线程池。
创建出来的线程池为ExecutorService类型,使用ExecutorService线程池执行线程任务的方法主要有submit方法 和 execute方法
public interface ExecutorService extends Executor {
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
/**
* 调用之后,不会再接收新的任务请求,会执行完再队列中的任务然后关闭线程池
*/
void shutdown();
}
public interface Executor {
void execute(Runnable command);
}
submit(…)方法与execte(…)方法的区别:
- 接收的参数不同
submit方法可以接收Runnable接口类型和Callable接口类型的参数
execute方法只能接收Runnable接口类型的参数 - 返回值不同
submit会返回Future类型的返回值,所以可以获取线程的返回值
execute没有返回值 - 所属接口不同
submit方法属于ExecutorService接口
execute方法属于Executor接口
使用线程池创建线程示例:
public class TestExecutors {
public static void main(String[] args) throws InterruptedException {
// 创建线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 创建runnable对象
Runnable runnable = () -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + " 正在执行");
};
for (int i = 0; i < 10; i++) {
// 使用线程池执行runnable对象的任务
executorService.submit(runnable);
}
// 执行完毕之后,线程池并不会关闭,所以JVM也不会关闭。
// 想要关闭线程池对象,需要使用手动关闭
executorService.shutdown();
}
}
注意:
这里使用Executors创建线程池只是一个演示,实际生产环境不允许使用Executors创建线程池。