多线程是 Java相关开发者不可跳过的环节,服务端,客户端都会遇到,所以今天借着机会,巩固一下多线程相关的知识。前篇主要讲述多线程的基础知识,从创建和启动入手,然后再介绍线程的状态,下篇主要讲解线程间的操作同步等
1. 线程创建和启动
线程的创建可以有多种方式,常见的为继承 Thread 和 实现 Runnable 借口,当然 也有直接通过new Thread()
方式创建线程。这里我们主要掌握Thread
以及Runable
方式
1.1 继承 Thread 类
先上代码
//MyThread.java
public class MyThread extends Thread{
@Override
public void run() {
doSomething() //需要执行的操作
}
}
// Main.java
public class Mian{
public static void main(String[] args){
MyThread thread = new MyThread(); //实例化线程
thread.start() //创建和启动线程
}
}
通过上述代码可以看到,我们需要覆写run
方法,在这个方法里面添加我们需要执行的操作,然后创建线程的时候只需要将MyThread
实例化,最后通过start()
方法启动线程即可。
1.2 实现 Runnable接口
在讲通过 Runnable 之前,我们先看一下 Thread 源码,如下图:
通过代码我们可以发现,其实 Thread 也是实现了 Runable 接口,相当于是 JDK 已经帮我们封装好了一个启动线程的类,所以接下来就很简单了,直接上代码
// MyRunnable.java
class MyRunnable implements Runnable{
@Override
public void run() {
doSomething(); //需要执行的操作
}
}
// Main.java
public class Mian{
public static void main(String[] args){
MyRunnable runnable = new MyRunable();
Thread thread = new Thread(runnable); //实例化线程
thread.start(); //创建和启动线程
}
}
通过代码我们发现,其实 Runnable
接口的方式和Thread
方式基本一样,最终还是通过实例化 Thread
方式,然后调用 Thread
的 start()
方法创建和启动线程的。
拓展
-
如何获取当前的线程名称?
通过
Thread.*currentThread*().getName()
即可获取当前线程名称 -
细心的同学可能已经注意到
new Thread
这里的代码,我注释的是实例化线程,而非创建线程。因为通过源码我们可以发现,new Thread
的时候,只是进行一些属性的赋值,而真正创建和启动线程是通过start()
方法,调用了 native 的start0()
这个方法实现。这一点我们从源码的注释可以确认,调用start()
方法会有两个线程返回值,一个是start()
的返回,一个是run()
方法的返回.
-
上面代码分析我们得知 Thread 内部的 run 方法是在另外一个线程中执行的,如果我们直接在主线程中调用会是什么结果呢?
答案就是,如果在主线程中调用,这个方法就是个普通方法,也就是
Thread
这个实例的方法,还是执行在主线程中。
1.3 使用 Callable 和 FutureTask
以上两种方式,都是通过 Runnable 的 run()方法执行,因为 run 方法是 没有返回值的,如果想要在子线程中获取返回值,则可以通过 Callable + FutureTask方式
首先看 Callable 接口代码:
public interface Callable<V> {
V call() throws Exception;
}
可以看到和 Runnable 不同的是,call 方法是有返回值的且可以抛出异常,另外 Callable 借口也是有一个泛型参数,call 的返回值就是泛型指定的类型。
那么如何通过 Callable+Future 方式创建线程呢?我们看下代码:
// MyCallable.java
class MyCallable implements Callable<Object>{
@Override
public Object call() throws Exception {
doSomething();
return result;
}
}
// Main.java
public class Mian{
public static void main(String[] args){
MyCallable callable = new MyCallable(); //实例化 Callable
FutureTask task = new FutureTask<>(callable); // 创建 future task
Thread thread = new Thread(task); // 实例化线程
thread.start(); //创建和启动线程
try{
task.g et(); // 获取子线程返回结果
}catch(Exception e){
doSomething();
}
}
}
以上就是通过 Callable 方式创建线程。可以看出相比 Runnable 方式,Callable 方式更为复杂
拓展
首先 FutureTask 这个类,实现了 RunnableFuture 接口,RunnableFuture 是继承了 Runnable 以及 Future接口。所以其本身就是一个 Runnable 的实现。这也就解释了为什么可以通过 new Thread方式来完成线程的创建和启动。
从上面我们知道 Thread 最终会在线程中调用 run 方法,所以我们来看一下 run 方法:
// FutureTask#run()
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //执行 call 方法
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result); // set 到当前对象中
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
从上面代码可以看出 首先通过 执行Callable 对象的 call()
方法获取返回的结果,然后讲结果通过 set(result)
方法设置到 FutureTask对象中。所以这也解释了为什么最后可以通过 get()
方法拿到返回值