一.如何给线程传递数据
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;
}
}