我们常说的高并发中的并发并不一定要依赖多线程,如PHP中很常见的并发都是进程级别的多进程并发。但是在Java里面谈论并发,大多数都与线程脱不开关系。那说到线程,就说说Java中线程的基础知识
在书中,将Java线程的生命周期分为了5种状态。我这里将它细分为7种状态,方便理解。
Java中线程的创建有三种方式
继承Thread类,并重写run()方法。
实现Runnable接口,并重写run()方法。
通过Callable和Future创建线程
步骤
示例
/**
* Java中创建线程方式一:继承Thread类
*/
public class ThreadTest extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
}
}
打印结果
0
1
2
3
4
5
6
7
8
9
步骤
示例
/**
* Java中创建线程方式二:实现Runnable接口
*/
public class RunnableTest implements Runnable{
@Override
public void run() {
for (int i = 100; i < 110; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}
打印结果
100
101
102
103
104
105
106
107
108
109
一般创建线程时,使用上面两种方式居多。但是这两种方式都有一个缺陷:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Callable接口可以理解成一段可以调用并返回结果的代码(call方法);
Future接口表示异步任务,是还没有完成的任务给出的未来结果。
所以说Callable用于产生结果,Future用于获取结果。这点可以在源码里面分析得知。
先看Runnable源码
Runnable位于java.lang包下,它是一个接口,在它里面声明了一个方法叫做 run():
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
再看Callable源码
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():
@FunctionalInterface
public interface Callable {
V call() throws Exception;
}
可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Future源码
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。为什么这么说呢?看了它的源码就知道了。
Future类位于java.util.concurrent包下,它也是一个接口
package java.util.concurrent;
public interface Future {
/**
* 取消任务
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 任务是否被取消成功
*/
boolean isCancelled();
/**
* 任务是否已经完成
*/
boolean isDone();
/**
* 获取执行结果
*/
V get() throws InterruptedException, ExecutionException;
/**
* 获取执行结果
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
所以说Future一共给我们提供了三种功能:
但是因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask实现于RunnableFuture接口,这个接口的定义如下:
public interface RunnableFuture extends Runnable, Future {
void run();
}
可以看到这个接口实现了Runnable和Future接口,接口中的具体实现由FutureTask来实现。这个类的两个构造方法如下 :
public FutureTask(Callable callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
如上提供了两个构造函数,一个以Callable为参数,另外一个以Runnable为参数。这些类之间的关联允许你基于FutureTask的Runnable特性(因为它实现了Runnable接口),把任务写成Callable,然后封装进一个由执行者调度并在必要时可以取消的FutureTask。
FutureTask可以由执行者调度,它对外提供的方法基本上就是Future和Runnable接口的组合:get()、cancel、isDone()、isCancelled()和run(),而run()方法通常都是由执行者调用,我们基本上不需要直接调用它。
步骤
示例
/**
* Java中创建线程方式三:Callable和FutureTask结合使用
*/
public class CallableTest implements Callable{
@Override
public Object call() throws Exception {
int i = 1000;
for ( ; i < 1010; i++) {
System.out.println(i);
}
return 1111;
}
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
FutureTask futureTask = new FutureTask(callableTest);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println("Result:"+futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
打印结果
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
Result:1111
优势是:
劣势是:
优势是:
劣势是:
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。