1.为什么要用多线程
之前一直对线程的概念很模糊,觉得特别抽象,后面看了许多的资料和书籍才知道,在这里假设一个场景,假如我们不用多线程,用一个线程就去把事给做了,举个栗子,当一个http请求过来,刚开始还好,我们去解析它的报文,但是在需要用字段作为条件去表里去查数据的时候,如果这张表里的数据特别多,那么该线程就需要去等待数据库系统返回值回来,这样就白白的浪费了时间。假如这个时候使用多线程,当前线程在等待数据库系统的返回值同时,另一个线程负责去渲染HTML页面,这样,当数据库返回值回来后,页面也可以直接渲染了。那么这就可以提高效率了。
另外也可以以物理电脑的例子来说明。现在为了提高的手机或者电脑设备的效率(本质上是为了解决处理器和内存存取之前的矛盾,处理器速度特别快,而内存存取速度慢),都是使用的多核处理器,每一个处理器都有对应的高速缓存,执行任务的过程中,因为涉及IO等耗时操作,十分影响性能,所以暂时将数据存储到存取速度快的高速缓存中,当执行完一个任务后将数据存到主存中。处理器,高速缓存关系如下。
Java内存模型1与物理机的模型相似。每个Java线程都有自己的工作内存(对应物理机中的高速缓存)。最后执行完任务后再存到主内存中的,在此过程中可能会有线程安全问题的出现,不过这待到后面的几篇文章详细解释。下图是线程,主内存,工作内存三者的交互关系图。
2.怎么实现多线程
我们解决了为什么,这个时候就需要解决怎么去实现多线程,共有四种方法
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
4)使用线程池例如用Executor框架
具体怎么实现很简单,在这里先不说,在这里着重讲两点
Thread和runnable的区别
runnable本身只是一个接口,不能实现多线程,这是runnable的源码
runnable接口之所以可以实现多线程是因为Thread类的构造方法,Thread构造方法中可以传递一个runnable类型的参数进来。下图是Thread类的构造方法
使用runnable实现多线程用法
首先创建一个CycleWait,它是一个实现了runnable接口的类
使用runnable实现多线程用法如下图,
针对runnable和thread讲两点
第一.Thread类如果看源码其实是实现了Runnable 接口的类, 使得run 支持多线程。下图是Thread类里的方法,通过调用run方法,因为我们创建的类实现了runnable接口,因此依照依赖倒置原则,则会调用我们创建类的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
第二.因类的单一继承原则, 推荐多使用RunnabIe 接口
使用Callable方式实现多线程可以获得子线程的返回值
与继承Thread,实现runnable接口不同,实现callable实现多线程的方式可以获得线程的返回值,下面我们通过查看futureTask的源码的方式看它如何实现返回值。
FutureTask类的构造方法
下图是它的构造方法,构造方法的参数接收一个实现callable的类
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
FutureTask类的三个重要的方法
isDone方法用来判断该线程是否已经执行完成,我们先记住他
public boolean isDone() {
return state != NEW;
}
get()方法用来,阻塞当前调用它的线程直到完成(如何阻塞的?看下面的if语句,如s表示状态,如果状态是在COMPLETING,这个单词翻译过来就是完成中,如果小于这个值说明未完成,因此调用awaitDone方法进行阻塞),如果完成就执行return语句,将返回值返回回去。
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
跟上面的get()方法一样,但是加上了一个时间,如果超过了这个时间该线程还没结束,就抛时间超时异常。
/**
* @throws CancellationException {@inheritDoc}
*/
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
FutureTask接收返回值的用法
创建一个类,使用Callable方式实现多线程,在call方法里设置value值,准备返回给调用者。
创建一个FutureTaskDemo类,去获得上面MyCallable类中返回的值。
至于为什么可以像我图中标识的那样去创建线程。因为我们可以从Thread类中可以知道Thread的构造方法中并没有参数为FutureTask类型的构造方法,但是可以去FutureTask类的源码中查看可知它是实现了RunnableFutrure接口。而RunnableFutrure接口则继承了Runnable类。而Thread的构造方法中是有参数类型为Runnable类型的参数的。
public class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
这篇解决了为什么要用到多线程以及如何实现多线程,并在最后简单的讲了一下实现线程的返回值,也算是线程之间的通信的范畴。下篇详细的讲一下线程之间如何进行的。
这里说的Java内存模型和JVM内存结构是不一样的概念不要搞混淆了 ↩︎