文章目录
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,
一、继承Thread类
通过继承Thread类来创建并启动多线程的一般步骤如下:
1、定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2、创建Thread子类的实例,也就是创建了线程对象。
3、启动线程,即调用线程的start()方法。
public class MyThreadTest1 {
public static void main(String[] args) {
Thread thread1 = new MyThread1("线程1");
Thread thread2 = new MyThread2("线程2");
thread1.start();
thread2.start();
}
// //继承Thread类
static class MyThread1 extends Thread {
public MyThread1(String name) {
super(name);
}
// 重写run方法
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
}
}
}
static class MyThread2 extends Thread {
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
}
}
}
}
二、实现Runnable接口
通过实现Runnable接口创建并启动线程一般步骤如下:
1、定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体。
2、创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象。
3、通过调用线程对象的start()方法来启动线程。
public class MyThreadTest2 {
public static void main(String[] args) {
new Thread(new MyRunnable1(), "线程1").start();
new Thread(new MyRunnable2(), "线程2").start();
}
static class MyRunnable1 implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
}
}
}
static class MyRunnable2 implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-------第" + i + "次执行");
}
}
}
}
三、实现Callable接口
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大,call()方法可以有返回值,call()方法可以声明抛出异常。
1、Callable创建线程的原理
执行 Callable 方式,需要 FutureTask实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类。
Callable接口是JAVA新增的接口,而且它不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。还有一个原因就是:call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的,所以这里涉及获取call()方法返回值的问题。
于是,JAVA5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。
继承关系:
public class FutureTask<V> implements RunnableFuture<V> {
/*
* Revision notes: This differs from previous versions of this
* class that relied on AbstractQueuedSynchronizer, mainly to
* avoid surprising users about retaining interrupt status during
* cancellation races. Sync control in the current design relies
* on a "state" field updated via CAS to track completion, along
* with a simple Treiber stack to hold waiting threads.
*
* Style note: As usual, we bypass overhead of using
* AtomicXFieldUpdaters and instead directly use Unsafe intrinsics.
*/
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
在Future接口里定义了几个公共方法来控制它关联的Callable任务:
public interface Future<V> {
/**
* 视图取消该Future里面关联的Callable任务
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 如果在Callable任务正常完成前被取消,返回True
*/
boolean isCancelled();
/**
* 若Callable任务完成,返回True
*/
boolean isDone();
/**
* 返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
*/
V get() throws InterruptedException, ExecutionException;
/**
* 返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:
2、创建步骤
- (1)创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例。
- (2)使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
- (3)用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口。
- (4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
public class MyCallableTest {
public static void main(String[] args){
// 2、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
// 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable1());
// 3、使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
new Thread(futureTask).start();
Integer integer = null;
try {
// 4、调用FutureTask对象的get()方法来接受子线程执行结束后的返回值,所有的线程没有执行完成之后这里是不会执行的
integer = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(integer);
}
// 1、创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例。
static class MyCallable1 implements Callable<Integer> {
// 方法的返回值类型与上面Callable后面的泛型一致
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
return sum;
}
}
}
四、通过线程池创建
线程池提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提交了响应速度。线程池创建线程我们后面详细讲解,这里暂时不深入展开。
五、三种创建线程方法对比
上面已经介绍完了JAVA中创建线程的三种方法,通过对比我们可以知道,JAVA实现多线程可以分为两类:一类是继承Thread类实现多线程;另一类是:通过实现Runnable接口或者Callable接口实现多线程。
下面我们来分析一下这两类实现多线程的方式的优劣:
1、继承Thread和实现Runnable
- 由于java中的单继承的限制,一个类只能继承一个类但是可以实现多个接口,所有更推荐实现Runnable接口的创建线程的方式。
- 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类,所以在实际工作中通常使用的是实现Runnable接口的创建线程的方式。
- 通过实现Runnable接口,多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况。
2、实现Runnable和实现Callable
- Callable规定的方法是call(),而Runnable规定的方法是run()。
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
- call()方法可抛出异常,而run()方法是不能抛出异常的。
- 运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
- 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
- Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。