java實現多線程的四種方式及多線程源碼分析

寫在前面

本文描述了java實現多線程的四種方式,文章可能還有很多不足,請大家諒解,歡迎大佬提意見。

本文使用到的東西

  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和線程池這一塊描述的較少,後期有時間再補充,或者重新開貼。有不清楚的地方歡迎評論留言,看到的我都會回覆的。本文到此結束,有什麼不足的地方請大家不吝指正。

發佈了44 篇原創文章 · 獲贊 29 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章