一.如何給線程傳遞數據
1.通過構造方法傳遞數據
在創建線程時,必須要創建一個Thread類的或其子類的實例。因此可以在調用start方法之前,通過 線程類的構造方法
將數據傳入線程。並將傳入的數據使用 成員變量接收
,以便線程體使用
/**
* TODO 測試線程傳遞參數1-通過構造方法傳遞數據
*/
public class TestThreadPassParam1 extends Thread {
//用於接受構造方法傳進來的數據
private String name;
//構造方法
public TestThreadPassParam1(String name) {
this.name = name;
}
//線程體
@Override
public void run() {
System.out.println("hello " + name);
}
public static void main(String[] args) {
Thread thread = new TestThreadPassParam1("world");
thread.start();
}
}
通過構造方法傳遞數據的,在線程運行之前這些數據就就已經到位了,這樣就不會造成數據在線程運行後才傳入的現象。如果要傳遞更復雜的數據,可通過類方法或類變量來傳遞數據。
2.通過變量和方法傳遞數據
向對象中傳入數據一般有兩次機會,第一次機會是在建立對象時通過構造方法將數據傳入,另外一次機會就是在類中定義一系列的public的方法或變量(也可稱之爲字段)。然後在建立完對象後,通過對象實例逐個賦值。下面的代碼是對MyThread1類的改版,使用了一個setName方法來設置name變量:
/**
* @Description TODO 測試線程傳遞參數2-通過變量和方法傳遞數據
*/
public class TestThreadPassParam2 extends Thread {
//傳遞參數
private String food;
public void setFood(String food) {
this.food = food;
}
//線程體
@Override
public void run() {
System.out.println("喫" + food);
}
public static void main(String[] args) {
TestThreadPassParam2 myThread = new TestThreadPassParam2();
myThread.setFood("包子");
Thread thread = new Thread(myThread);
thread.start();
}
}
3.通過回調函數傳遞數據
上面兩種向線程傳遞數據的方法是最常用的。都是在主線程
main()中主動
將數據傳入線程類
的。這對於線程來說,是被動接收這些數據的。然而,在有些應用中需要在線程運行的過程中動態地獲取數據.
如: 在下面代碼的run方法中產生了3個隨機數,然後通過Work類的process方法求這三個隨機數的和,並通過Data類的value將結果返回。從這個例子可以看出,在返回value之前,必須要得到三個隨機數。也就是說,這個value是無法事先就傳入線程類的。
import java.util.Random;
/**
* @Description TODO 測試線程傳遞參數3-通過回調函數傳遞數據
*/
public class TestThreadPassParam3 extends Thread {
//傳遞參數
private Work work;
//構造方法
public TestThreadPassParam3(Work work) {
this.work = work;
}
//線程體
public void run() {
//聲明Data對象
Data data = new Data();
//聲明隨機對象
Random random = new Random();
int n1 = random.nextInt(1000);//隨機數1
int n2 = random.nextInt(2000);//隨機數2
int n3 = random.nextInt(3000);//隨機數3
//線程內調用work對象的.process方法
work.process(data, n1, n2, n3); // 使用回調函數
System.out.println(n1 + "+" + n2 + "+" + n3 + "=" + data.value);
}
public static void main(String[] args) {
Thread thread = new TestThreadPassParam3(new Work());
thread.start();
}
}
//計數器
class Data {
public int value = 0;
}
//在線程內回調,用於將numbers的值累加到Data.value
class Work {
public void process(Data data, Integer... numbers) {
for (Integer n : numbers) {
data.value += n;
}
}
}
在上面代碼中的process方法
被稱爲回調函數。從本質上說,回調函數就是事件函數。調用回調函數的過程就是最原始的觸發事件的過程
。在這個例子中調用了process方法
來獲得數據也就相當於在run方法中引發了一個事件。
二.如何讓線程返回數據
從線程中返回數據和向線程傳遞數據類似,也可以通過類成員以及回調函數來返回數據,但類成員在返回數據和傳遞數據時有一些區別
1.主線程等待
使用這種方法返回數據需要在線程完成後才能通過類變量或方法得到數據
/**
* TODO 測試線程返回數據1-通過主線程等待返回數據
*/
public class TestThreadReturnData1 extends Thread {
private Count count;
TestThreadReturnData1(Count count) {
this.count = count;
}
@Override
public void run() {
for (int i =0 ;i<10;i++) {
this.count.setValue(this.count.getValue()+1);
}
}
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
Thread thread = new Thread(new TestThreadReturnData1(count));
thread.start();
// 獲取子線程的返回值:主線程等待法
while (count.getValue() == 0) {
Thread.sleep(1000);
}
System.out.println(count.getValue());
System.out.println("主線程執行完畢");
}
}
//計數器
@Data
class Count {
public int value = 0;
}
2.Join方法阻塞當前線程以等待子線程執行完畢
使用join方法可以讓子線程執行完畢後再執行主線程,本質和通過循環判斷一樣
import lombok.Data;
/**
* @Description TODO 測試線程返回數據1-通過Join方法阻塞當前線程以等待子線程執行完畢返回數據
*/
public class TestThreadReturnData2 extends Thread {
private Count2 count;
TestThreadReturnData2(Count2 count) {
this.count = count;
}
@Override
public void run() {
for (int i =0 ;i<10;i++) {
this.count.setValue(this.count.getValue()+1);
}
}
public static void main(String[] args) throws InterruptedException {
Count2 count = new Count2();
Thread thread = new Thread(new TestThreadReturnData2(count));
thread.start();
// 獲取子線程的返回值:Thread的join方法來阻塞主線程,直到子線程返回
thread.join();
System.out.println(count.getValue());
System.out.println("主線程執行完畢");
}
}
//計數器
@Data
class Count2 {
public int value = 0;
}
3.使用Callable接口和FutureTask
在JDK1.5加入了Callable接口,實現該接口並重寫call()方法也能創建新線程,並且該方法是有返回值的!
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableAndFutureReturnData implements Callable<String> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/***
* FutureTask 實現了 Runnable接口,所以新建線程的時候可以傳入FutureTask
* FutureTask重寫的run方法中實際是調用了Callable接口在call()方法,所以執行線程的時候回執行call方法的內容
*/
FutureTask<String> task = new FutureTask<String>(new CallableAndFutureReturnData());
new Thread(task).start();
if (!task.isDone()) {
System.out.println("task has not finished, please wait!");
}
System.out.println("task return: " + task.get());
}
@Override
public String call() throws Exception {
String value = "over";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("task done");
return value;
}
}
這裏需要關注FutrueTask的兩個方法:
- isDone:利用state變量判斷call方法有沒有被執行
- get:如果call方法已經執行完就返回call方法的返回值,如果call方法沒有執行完就一直阻塞
4.使用線程池
public class ThreadPoolDemo {
public static void main(String[] args) {
//創建線程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//通過線程池管理線程MyCallable
Future<String> future = newCachedThreadPool.submit(new MyCallable());
//如果提交的任務未完成
if (!future.isDone()) {
System.out.println("task has not finished, please wait!");
}
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
//關閉線程池
newCachedThreadPool.shutdown();
}
}
}
class MyCallable implements Callable<String> {
//線程體
@Override
public String call() throws Exception {
String value = "over";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("task done");
return value;
}
}