本文使用到的东西

  1. eclipse 2019-11
  2. 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源码分析

  1. 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) 
  1. Thread实现了Runnable接口,所以Thread必须实现run()方法。
//Thread实现了Runnable接口
public class Thread implements Runnable {
  1. run()方法,线程执行的内容主体
	//Runnable接口的实现类对象
	private Runnable target;
	...
	//run方法,线程执行的内容
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

  可以看到在Thread中,run()方法判断了是否存在Runnable接口的实现类对象,如果存在则执行该对象的run()方法,如果不存在该方法就不执行任何内容,所以我们要实现多线程有两种方法:①复写run()方法;②传入Runnable实现类对象。
  在这里我们也可以看到执行run()方法并没有创建新的线程,接下来我们看看start()方法。

  1. 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源码分析

  1. FutureTask也实现了Runnable接口
public class FutureTask<V> implements RunnableFuture<V> {

public interface RunnableFuture<V> extends Runnable, Future<V> {

  从上面可以看出,FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable,和Future接口,最终FutureTask也是逃不过Runnable。

  1. 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和线程池这一块描述的较少,后期有时间再补充,或者重新开贴。有不清楚的地方欢迎评论留言,看到的我都会回复的。本文到此结束,有什么不足的地方请大家不吝指正。