前言:
前面一篇介绍了并发编程的一些是名词解释,并罗列了相关疑惑点。这篇将介绍在并发编程的实际应用
一、创建多线程的方式
概述:多线程创建的方式,有人说3种,有人说4种,其实具体数字并没有什么意义,在实际使用中,根据具体应用场景,选择合适的方式即可。下面介绍几种常见的多线程创建-使用方式
1、编写自己的任务类,继承Thread类,复写Thread类的run方法,在驱动线程中创建任务类的对象,调用start方法。将启动一个线程。执行该任务。
注意,这里强调编写自己的任务类,而不是编写自己的线程类,目的在于上一篇中说明的区分任务和线程的名词属性,避免混淆。
查看Thread的源码,其本质上是一个实现了Runnable接口的任务类
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
……
这种方式的使用
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread run 方法开始执行");
}
}
// 在函数中执行下面代码,将启动线程执行任务
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
2、编写自己的任务类,实现Runnable接口。并创建该任务类的实例作为参数传递给Thread类的构造器,实例化一个Thread类实例,并调用start方法,启动一个线程。这种方式与继承Thread类的优点在于,接口可以多实现。Java中类的继承只能是单继承
public class MyThread implements Runnable {
public void run() {
System.out.println("MyThread.run 方法执行了");
}
}
// 在函数中执行如下代码
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
3、编写任务类,实现Callable接口,该方式的优点在于可以拿到线程执行的返回结果。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import java.util.concurrent.Callable;
/**
* <p>Description: 定义任务-有返回值</p>
* <p>Copyright: @2018</p>
* <p>Company: YeePay</p>
*
* @author 小蚱蜢
* @version V1.0 2018/6/10
*/
public class MyCallable implements Callable<String> {
private static final Logger logger = LoggerFactory.getLogger(MyCallable.class);
private int id;
public MyCallable(int id){
this.id = id;
}
// 定义线程执行体
@Override
public String call() throws Exception {
// 这里模拟调用远程接口,做一些操作,返回时间大概在1-3s之间
int sleepTime = getThreadSleepTime();
Thread.sleep(sleepTime);
logger.info("模拟调用接口,耗时时间:{}, 模拟调用接口返回结果#{}",sleepTime,id);
return "模拟调用接口返回结果";
}
/**
* 模拟任务执行耗费时间
* @return
*/
private int getThreadSleepTime(){
Random random = new Random();
int nextInt = random.nextInt(3);
int sleepTime = 1000 * nextInt;
return sleepTime;
}
}
@Test
public void test_MyCallable(){
int taskCount = 1000000;
// 假设每个任务耗时1秒钟,执行完10个任务,需要耗时10秒
// 使用多线程调用
long timeStart = System.currentTimeMillis();
ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<String>> futureResult = new ArrayList<Future<String>>();
for (int i =0; i<taskCount ; i++){
logger.info("开始执行任务:{}",i);
Future<String> submitResult = executorService.submit(new MyCallable(i));
futureResult.add(submitResult);
}
for (Future<String> sigleFuture : futureResult){
logger.info("sigleFuture.isDone() :{}", sigleFuture.isDone());
// if (sigleFuture.isDone()){
// try {
// logger.info("Future has finished task:{}", sigleFuture.get());
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
// }
try {
logger.info("task result :{}", sigleFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
long endTime = System.currentTimeMillis();
logger.info("使用多线程执行{}个任务,每个任务大概耗时1秒,最终耗费时间:{}",taskCount, ((endTime-timeStart)/ 1000.00));
}
4、以上几种方式是显示的调用了Thread接口的start方法来驱动任务执行,更加常见的方式是通过线程池来管理,在上面Callable接口的例子中,通过Executors.newCachedThreadPool()来创建一个线程池。然后将任务类的实例提交给线程池,交由线程池来完成任务驱动。
以上四点是常见的线程使用方式。后面的章节中,将介绍线程池的具体配置,以及多线程编程中的一些常见问题,如果因为个人理解的偏差,文中若有不正确的地方,欢迎批评指正!