在编程中,我们很少接触到多线程,那是因为多线程都被封装到底层了,本来一切都相安无事,我们只需要愉快的CRUD就好了,直到有一天, 女朋友送了我一个2080Ti, 并且问我,游戏和我哪个重要?
我经过一晚上的思考,原本不富裕的头发又生生扯断了好几根,下了一个很沉痛的决定... 给显卡配了个10980XE,真的是哪来的自信敢和游戏比啊?总的来说呢体验都挺不错,扫雷体验挺不错,膝盖体验也挺不错
作为有显卡有女朋友的资深程序员,当然要先试一下新机了,然而我们却发现了一个很重要的问题
跑起程序来没啥变化呀,也没多快呀,cpu你是不是罢工了呀?打开任务管理器一看,好嘛,就四五个线程在运行,其他几十个都在呼呼睡大觉,银子白花的?榴莲白跪的?你们都给我走起...
既然我们决定让线程满额跑起,就先创建个线程吧,首先我们从最简单的开始:
继承Thread/实现Runnable
作为跟随jdk一开始便发布的元老级方法,自然古老而且简单,先上一个任务实体
import java.lang.reflect.InvocationTargetException;
public class Task implements Runnable {
// 实体
private Object entity;
// 方法名
private String methodName;
// 参数类型
private Class<?>[] parameterTypes;
// 参数值
private Object[] parameterValues;
public Task(Object entity, String methodName) {
this.entity = entity;
this.methodName = methodName;
}
public Task(Object entity, String methodName, Class<?>[] parameterTypes, Object[] parameterValues) {
this.entity = entity;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameterValues = parameterValues;
}
@Override
public void run() {
try {
entity.getClass().getDeclaredMethod(methodName, parameterTypes).invoke(entity, parameterValues);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
}
}
这里我们采用实现Runnable的方式创建任务线程类,我们之所以反射来执行任务,这么做的原因当然是因为解耦我们先简单的说一下这几个参数:
entity:执行对象
methodName:执行方法
parameterTypes:参数类型数组,也就是[参数类型.class], 如:String.class,另一种获取方式是[参数.getClass()]
parameterValues:参数值数组,也就是具体参数.参数和参数类型数组要一一对应
以上我们的任务类就创建好了,他的主要作用也就一个,执行任务
我们再创建一个测试类,然后给他创建一个固定线程数的线程池,这种线程也是最适合我们的业务场景,这种线程池一旦创建,便规定了线程的上限.线程池是从1.5开始被引入的,主要作用就在于执行和回收线程,在这里我们指定3个线程来执行任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
private static ExecutorService threadPool = Executors.newFixedThreadPool(3);
public String hehe(String name) {
System.err.println("我被执行啦");
try {
// 假装很忙...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("我完事了");
return "我是返回值";
}
public static void main(String[] args) {
//循环执行
while (true) {
threadPool.execute(new Task(new Test(), "hehe", new Class[] { String.class }, new String[] { "ss" }));
}
}
}
经过测试,发现确实可行
这时候,我们发现了一个很恐怖的事...
我们的返回值怎么获得呢?
毕竟重写的run方法是一个无返回值的方法,若是你用日志保存返回值当我没说除此之外,这种方法也作为执行无返回值的多线程方法确实也是一个可选之方案
实现Callable
为了弄到我们的返回值,我们首先创建一个线程类Multithreading,这样物品们线程数我们就可以作为参数传入
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Multithreading {
private static ExecutorService threadPool;
private Multithreading() {
}
public static void excute(Task task, int threadNum) {
threadPool = Executors.newFixedThreadPool(threadNum);
CompletionService<Object> service = new ExecutorCompletionService<Object>(threadPool);
//try {
while (true) {
Future<Object> future = service.submit(task);
//System.err.println(future.get());
}
//} catch (InterruptedException | ExecutionException e) {
// e.printStackTrace();
//}
}
}
这里我们看到了几个新朋友:
CompletionService:他相较于我们的线程池多一个BlockingQueue的功能,BlockingQueue可以异步保存执行结果,除此之外然后还有一个作用是限制任务时间, 干不了就别干了,浪费时间
Future:可以理解为执行结果,在我们的逻辑上future.get()获取的就是返回值
为啥注释好几行代码呢?容我先卖个关子
为了使用CompletionService,我们简单的把Task的实现对象换成Callable<Object>, 把在run里面的方法直接放到call里,然后加个返回值,于是我们的Task类他变了
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Callable;
public class Task implements Callable<Object> {
// 实体
private Object entity;
// 方法名
private String methodName;
// 参数类型
private Class<?>[] parameterTypes;
// 参数值
private Object[] parameterValues;
public Task(Object entity, String methodName) {
this.entity = entity;
this.methodName = methodName;
}
public Task(Object entity, String methodName, Class<?>[] parameterTypes, Object[] parameterValues) {
this.entity = entity;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameterValues = parameterValues;
}
@Override
public Object call() {
Object result = null;
try {
result = entity.getClass().getDeclaredMethod(methodName, parameterTypes).invoke(entity, parameterValues);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
}
我们再修改下测试方法来看看
public class Test {
public String hehe(String name) {
System.err.println("我被执行啦");
try {
//假装很忙...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("我完事了");
return "我是返回值";
}
public static void main(String[] args) {
Multithreading.excute(new Task(new Test(), "hehe", new Class[] {String.class}, new String[]{"ss"}), 3);
}
}
执行下试试
突然发现,我又行了
等等,这次我们就是为了获取返回值而做的改变,当然要看下返回值
让我们解开注释
封印解除!!!
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Multithreading {
private static ExecutorService threadPool;
private Multithreading() {
}
public static void excute(Task task, int threadNum) {
threadPool = Executors.newFixedThreadPool(threadNum);
CompletionService<Object> service = new ExecutorCompletionService<Object>(threadPool);
try {
while (true) {
Future<Object> future = service.submit(task);
System.err.println(future.get());
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
然后执行,我们发现了一个惊人的事实
马...马...马萨卡???为啥不是"我被执行完了"先被打印出三遍,而是执行之后而出结果?这样你以顺序执行的话我创建你多线程还有毛用?
遍寻秘籍经典,经过好几分钟我们找到了文档发现,future.get()是阻塞方法.WTF?你在那阻塞着我还多线程啥啊?幸好Java留有后手,所以说Java自己的需求估计会比我们更坑爹
实现Supplier
我们再将Multithreading类给小小的变动一下,实现一下Supplier<Object>
import java.lang.reflect.InvocationTargetException;
import java.util.function.Supplier;
public class Task implements Supplier<Object> {
// 实体
private Object entity;
// 方法名
private String methodName;
// 参数类型
private Class<?>[] parameterTypes;
// 参数值
private Object[] parameterValues;
public Task(Object entity, String methodName) {
this.entity = entity;
this.methodName = methodName;
}
public Task(Object entity, String methodName, Class<?>[] parameterTypes, Object[] parameterValues) {
this.entity = entity;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameterValues = parameterValues;
}
@Override
public Object get() {
Object result = null;
try {
result = entity.getClass().getDeclaredMethod(methodName, parameterTypes).invoke(entity, parameterValues);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
}
然后修改下我们的Multithreading,这里我们又多了一个新朋友:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Multithreading {
private static ExecutorService threadPool;
private Multithreading() {
}
public static void excute(Task task, int threadNum) {
threadPool = Executors.newFixedThreadPool(threadNum);
while (true) {
CompletableFuture.supplyAsync(task,threadPool).whenComplete((result, e) -> {
System.err.println(result);
}).exceptionally((e) -> {
System.err.println(e);
return "exception";
});
}
}
}
CompletableFuture:相较于Future.get()最关键的两点就是:异步然后和判断执行结果,其他都不重要
然后测试类不用做什么改变,走他
我们惊喜的发现...终于和我们想的一样了
就这样,我们的学习就告一段落了,啥都不说了