本文使用到的东西
- eclipse 2019-11
- java
1.继承Thread类
1.1实现代码
class MyThread extends Thread{
private int num=0;
public void run() {
for(int i=0;i<6;i++) {
System.out.println(getName()+": num="+num++);
}
}
}
public class 多线程 {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
1.2源码分析
- Thread的实例化方式
//Runnable接口实现
Thread(Runnable target)
//Runnable接口实现,线程名
Thread(Runnable target, String name)
//设置线程所属的组
Thread(ThreadGroup group, Runnable target)
//设置线程所属的组,线程名
Thread(ThreadGroup group, Runnable target, String name)
//设置线程所属的组,线程名,当前线程栈大小
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
- Thread实现了Runnable接口,所以Thread必须实现
run()
方法。
//Thread实现了Runnable接口
public class Thread implements Runnable {
run()
方法,线程执行的内容主体
//Runnable接口的实现类对象
private Runnable target;
...
//run方法,线程执行的内容
@Override
public void run() {
if (target != null) {
target.run();
}
}
可以看到在Thread中,run()
方法判断了是否存在Runnable接口的实现类对象,如果存在则执行该对象的run()
方法,如果不存在该方法就不执行任何内容,所以我们要实现多线程有两种方法:①复写run()方法;②传入Runnable实现类对象。
在这里我们也可以看到执行run()
方法并没有创建新的线程,接下来我们看看start()
方法。
start()
创建新线程
//线程的状态,默认为0,表示线程未启动
private volatile int threadStatus;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
//通知该线程所在的group该线程已经启动
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
//通知该线程所在的group该线程已经关闭
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
start()
添加了synchronized
修饰符,该方法不能多线程执行,执行一开始判断了线程状态threadStatus
是否为0(未启动),如果线程启动过则抛出IllegalThreadStateException
异常,所以start()
方法只能执行一次。
最终start()
执行了start0()
方法,它是启动多线程的逻辑。这是一个本地方法(JNI),无论是哪一种方式实现多线程,最终都是通过这个本地方法来启动多线程。因为多线程的实现和系统有关,通过JNI实现与具体系统相关的功能,是java实现跨平台的关键。
2.实现Runnable接口
2.1实现代码
class MyRunnable implements Runnable{
private int num=0;
@Override
public void run() {
for(int i=0;i<6;i++) {
System.out.println(Thread.currentThread().getName()+": num="+num++);
}
}
}ublic class 多线程 {
public static void main(String[] args) {
Runnable runnable=new MyRunnable();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
2.2 源码分析
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
可以看到Runnable接口只定义了一个“run()
”抽象方法,该方法是线程执行内容的主体。
其实通过Thread源码分析我们就知道多线程的实现是通过Thread的start0()
方法实现的,Runnable只是一个用于提供线程执行内容的接口,如果没有Thread多线程是无法实现的。
3.实现Callable
3.1实现代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer>{
private int num=0;
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<5;i++) {
sum+=num;
System.out.println(Thread.currentThread().getName()+": num="+num++);
}
return sum;
}
}
public class 多线程 {
public static void main(String[] args) {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> task1=new FutureTask<Integer>(callable);
FutureTask<Integer> task2=new FutureTask<Integer>(callable);
new Thread(task1).start();
new Thread(task2).start();
try {
System.out.println("任务1="+task1.get()+", 任务2="+task2.get());
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e1) {
e1.printStackTrace();
}
}
}
3.2 Callable源码分析
1.Callable接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
与Runnable有些类似,Callable接口也只是提供了一个执行多线程内容的接口,不同的是它使用泛型定义了一个返回值,返回值为线程执行后的结果,而且该方法可以抛出异常。
3.3 FutureTask源码分析
- FutureTask也实现了Runnable接口
public class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {
从上面可以看出,FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable,和Future接口,最终FutureTask也是逃不过Runnable。
- Future接口
public interface Future<V> {
//尝试取消任务的执行,mayInterruptIfRunning表示是否应该中断执行此任务的线程以尝试停止该任务
boolean cancel(boolean mayInterruptIfRunning);
//任务是否在完成前被取消
boolean isCancelled();
//判断线程任务是否完成
boolean isDone();
//阻塞线程,直到获取到任务执行结果
V get() throws InterruptedException, ExecutionException;
//timeout等待时间,unit等待时间的单位,等待时间到了获取任务执行结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
对于FutureTask的源码分析简单到此,以后有时间再开个帖子具体分析。但是要注意如果任务一直在执行,get()
方法将会导致进程阻塞。
4.线程池Executors实现
4.1实现代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MyThread extends Thread{
private int num=0;
public void run() {
for(int i=0;i<6;i++) {
System.out.println(getName()+": num="+num++);
}
}
}
class MyRunnable implements Runnable{
private int num=0;
@Override
public void run() {
for(int i=0;i<6;i++) {
System.out.println(Thread.currentThread().getName()+": num="+num++);
}
}
}
class MyCallable implements Callable<Integer>{
private int num=0;
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<5;i++) {
sum+=num;
System.out.println(Thread.currentThread().getName()+": num="+num++);
}
return sum;
}
}
public class 多线程 {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(new MyThread());
executor.submit(new MyRunnable());
executor.submit(new MyCallable());
executor.shutdown(); //关闭线程池
}
}
5.Thread与Runnable比较
首先先比较上面代码中Thread和Runnable两种方式的执行结果:
Thread方式的结果:
Thread-0: num=0
Thread-0: num=1
Thread-0: num=2
Thread-1: num=0
Thread-1: num=1
Thread-1: num=2
Thread-1: num=3
Thread-0: num=3
Thread-1: num=4
Thread-1: num=5
Thread-0: num=4
Thread-0: num=5
Runnable的结果:
Thread-1: num=1
Thread-1: num=2
Thread-0: num=0
Thread-0: num=4
Thread-0: num=5
Thread-0: num=6
Thread-1: num=3
Thread-1: num=8
Thread-1: num=9
Thread-0: num=7
Thread-0: num=11
Thread-1: num=10
可以看到实现Runnable接口的方式共享了num
变量,而继承Thread方式并没有。因为实现Runnable接口的方式中,给两个Thread对象提供的是同一个Runnable接口实现类对象,两个线程实际上执行的是同一对象的同一方法,所以可以直接共享变量;而继承Thread的方式是通过复写run()
方法实现的,我们执行的两个线程的run()
方法分别属于两个不同的Thread对象,所以无法直接共享变量。
6.总结
本文主要的重心是在Thread这里,无论怎么实现多线程,最终都要通过Thread的start0()
方法来实现多线程,对于FutureTask和线程池这一块描述的较少,后期有时间再补充,或者重新开贴。有不清楚的地方欢迎评论留言,看到的我都会回复的。本文到此结束,有什么不足的地方请大家不吝指正。