寫在前面
本文描述了java實現多線程的四種方式,文章可能還有很多不足,請大家諒解,歡迎大佬提意見。
本文使用到的東西
- 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和線程池這一塊描述的較少,後期有時間再補充,或者重新開貼。有不清楚的地方歡迎評論留言,看到的我都會回覆的。本文到此結束,有什麼不足的地方請大家不吝指正。