继承Thread类:
继承Thread类重写run()方法,这个是在初学Java多线程的时候都了解的创建线程的方式。其中每一个继承Thread类的子类都要重写run()方法,run()方法作为每一个线程的核心方法。
- 第一步:继承Thread类,重写run()方法。
- 第二步:线程的启动,调用start()方法,将新创建的线程加入到就绪状态。其中run()方法是在操作系统创建完线程后回调的方法,不可以手动调用;手动调用run方法只是执行方法而已,并不是真的启动一个新的线程。
public class ThreadTest extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest();
ThreadTest t2 = new ThreadTest();
t1.setName("thread_t1");
t2.setName("thread_t2");
t1.start();
t2.start();
}
}
实现Runnable()接口:
在上面的方案中,通过继承Thread类重新run()方法明显存在一些不足,首先我们都知道Java是单继承,使用继承去实现一个多线程是很大的牺牲。这里给出实现接口的方案,实现runnable接口重写run方法。
/**
* 实现Runnable接口创建线程
*/
public class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
System.out.println("Thread name is " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(),"Thread_t1");
Thread t2 = new Thread(new MyThread(),"Thread_t2");
Thread t3 = new Thread(new MyThread(),"Thread_t3");
t1.start();
t2.start();
t3.start();
}
}
实现Callable接口:
对于初学多线程的小白,相信这个接触的比较少吧,Callable接口是Java.util.concurrent包下的一个接口,它也声明了一个方法,但是不是run()方法,而是call()方法。
为什么要有Callable接口呢,因为无论是继承Thread类还是实现Runnable接口都是没有办法获得线程的返回值的,但是实现Callable接口创建线程是可以获得线程的返回值的。
下面就是Callable接口:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
那么要怎么使用Callable接口呢?一般情况下是配合ExecutorService一起使用的,在ExecutoeService接口中声明类若干个Submit方法的重载版本:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
如上代码片段是ExecutorService接口中定义的方法,可以看到第一一个Submit方法的参数中类型是Callable,这里只需要了解Callable是和ExecutorService配合使用的就可以了,这里使用比较多的submit方法是第一个和第三个。
Future
在Callable介绍中可以看到,每一个submit的返回类型都和Future有关,那么Future又是什么呢?
Future就是对于具体的Runnable或者Callable任务执行结果进行取消、查询是否完成、获取结果、必要时可以通过get获取结果。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future中声明了5个方法,下面依次解释一下:
- cancel()方法是用来取消任务的,任务取消成功返回true,失败返回false。mayInterruptIfRunning参数表示是否允许取消正在执行的却没有完毕的任务,如果设置为true则表示取消正在执行过程中的任务。如果任务已经完成,无论mayInterruptIfRunning为true还是false都无法取消任务。
- isCancelled():表示任务是否取消成功,如果在任务正常完成之前取消成功则返回true;
- isDone():表示任务是否已经完成,true表示完成。
- get()方法用于获取线程执行结果,这个方法会产生阻塞,会一直等待任务执行完毕才返回。
- V get(long timeout, TimeUnit unit):用来获取线程执行结果,如果在指
定时间内,还没有返回结果,直接返回null。
这里可以看出Future提供了三种功能:
- 判断任务是否完成。
- 能够中断任务。
- 能够获取任务的返回结果。
是为Future只是一个接口,在具体使用的时候还是要使用实现类FutureTask。
FutureTask
这里先来看一下FutureTask的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask提供了两个构造器:
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
其中FutureTask是Future的实现类,也是唯一一个实现类。
Callable + Future使用示例:
/**
* 实现Callable接口创建线程
*/
public class CallableTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executorService.submit(task);
executorService.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程正在执行任务");
try {
System.out.println("子线程执行结果: " + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任务执行完毕");
}
static class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(2000);
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
}

如上面代码:
- 首先使用创建了一个Callable的任务Task。
- 将任务提交到ExecutorService线程池。
- 使用Future get()阻塞线程,获取子线程的返回结果。
通过线程池启动多线程:
使用Executor的工具类可以创建三种类型的普通线程池:
FixThreadPool(int n); 固定大小线程池
/**
* 使用FixThreadPool(int n)固定大小的线程池
*/
public class ThreadPool1 {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName() + " " + j);
}
}
});
}
executor.shutdown();
}
}
SingleThreadPool:单线程池
需要保证顺序执行的任务场景:
/**
* SingleThreadPool但线程池
*/
public class ThreadPool2 {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName() + " " + j);
}
}
});
}
executor.shutdown();
}
}
CachedThreadPool;缓存线程池
当提交任务速度高于线程池中任务的处理速度时,缓存线程池会不断的创建线程,适用于提交短期异步小程序,以及负载较轻的服务器;
/**
* CachedThreadPool 缓存线程池
*/
public class ThreadPool3 {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName() + " " + j);
}
}
});
}
executor.shutdown();
}
}
上面只是给出了四种创建线程的方式和案例,具体的一些细节和原理在其他文章中会给出。
本文详细介绍了在Java中创建多线程的三种主要方法:继承Thread类、实现Runnable接口和实现Callable接口。通过示例代码展示了每种方法的具体实现,并讨论了它们之间的差异和适用场景。

5943

被折叠的 条评论
为什么被折叠?



