分类
Java多线程实现方式主要有四种:
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口通过FutureTask包装器来创建Thread线程
4. 使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
普通多线程
1. 继承Thread类创建线程
Thread类再内部实现了Runnable接口,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法(currentThread.start())。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。创建一个类继承 Thread类,并在run()方法中写上自己的业务代码。
demo
package threads;
import java.util.Date;
/**
* @author liaowei
* @date 2019-11-21 15:55:57
* @decription
*/
public class ThreadTest {
// 基本多线程
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()" + new Date());
}
}
- 实现Runnable接口创建线程 ,可以实现一个Runnable接口
package threads;
import java.util.Date;
/**
* @author liaowei
* @date 2019-11-21 15:59:50
* @decription
*/
public class ThreadImpRun implements Runnable {
//接受参数
private String params;
//必须存在的构造函数,不然获取不到参数
public ThreadImpRun(String params) {
this.params = params;
}
@Override
public void run() {
System.out.println("欢迎关注公众号:【" + this.params + "】");
}
public static void main(String[] args) {
ThreadImpRun myThread = new ThreadImpRun("一抹浮云");
Thread thread = new Thread(myThread);
thread.start();
}
}
实用多线程
1.future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态。
①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
③、使用FutureTask对象作为Thread对象启动新线程。
④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
package threads;
import com.alibaba.fastjson.JSONObject;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author liaowei
* @date 2019-11-21 16:03:25
* @decription
*/
public class DempCallable<V> {
public static void main(String[] args) {
JSONObject params = new JSONObject();
params.put("key", " 一抹浮云 ");
Callable<JSONObject> callable = new Test<>(params);
//由Callable<JSONObject>创建一个FutureTask<JSONObject>对象:
FutureTask<JSONObject> oneTask = new FutureTask<>(callable);
//FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
//由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
oneThread.start();
//至此,一个线程就创建完成了。
}
}
class Test<V> implements Callable<V> {
private JSONObject params;
public Test(JSONObject params) {
this.params = params;
}
@Override
public V call() throws Exception {
System.out.println("公众号【" + params.get("key")+"】");
return null;
}
}
- ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,我们可以轻松获取多线程处理结果。可返回值的任务必须实现Callable接口。无返回值的任务必须实现Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。结合线程池接口ExecutorService就可以实现返回结果的多线程.
package threads;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
public class ExecutorsTest {
public static void main(String[] args) {
Date date1 = new Date();
List<JSONObject> datas = new ArrayList<>();
for (int i = 0; i < 10; i++) {
JSONObject data = new JSONObject();
data.put("key", "欢迎关注公众号:【一抹浮云】" + i);
datas.add(data);
}
int taskSize = 5;
// 创建一个线程池 :
/**
* 线程池有如下创建方式,根据实际需要选择合适的方式:
* newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
* newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
* newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
* newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
* newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
* newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
* ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
*/
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 根据自己的需要创建任务数量:比如你有10万条数据需要处理,可以将这些数据分组,建立分组基数 100000/10(任务数量:根据机器的空闲cpu来定)
//注意: 因为事测试 就不造太多数据,MyCallable中所有的params都用datas。实际开发中,应该是对datas进行分组,然后再根据分组数量创建任务数量
List<Future> list = new ArrayList<>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i, datas);
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
//捕获异常
try {
System.out.println("输出任务运行结果:" + f.get().toString());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object> {
private Integer taskNum;
private List<JSONObject> params;
public MyCallable(Integer taskNum, List<JSONObject> params) {
this.taskNum = taskNum;
this.params = params;
}
public Object call() throws Exception {
System.out.println("任务-" + taskNum + "-任务启动");
Date dateTmp1 = new Date();
String rs = "";
for (JSONObject param : this.params) {
if (StringUtils.isNotEmpty(rs)) {
rs += "," + param.getString("key");
} else {
rs = param.getString("key");
}
}
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println("任务-" + taskNum + "任务结束,返回任务处理结果");
return taskNum + "-任务返回运行" + rs + "处理任务消耗时间【" + time + "毫秒】";
}
}
优势对比:
继承Thread类和实现Runnable接口、实现Callable接口的区别。
继承Thread:线程代码存放在Thread子类run方法中
优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
劣势:Java单继承,继承了Thread类,无法再继承其他类。
实现Runnable:线程代码存放在接口的子类的run方法中。
优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:相对复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
实现Callable:
优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法