多线程基础之Runnable/Thread与Callable
线程的分类:
- 用户线程:运行在前台,执行具有的任务程序的主线程,连接网络的子线程等都是用户线程
- 守护线程:运行在后头,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作。可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程,该方法必须在start()方法之前调用,否则会抛出 IllegalThreadStateException异常。在守护线程中产生的新线程也是守护线程。不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。
一、Runnable入门示例
public class RunnableDemo1 {
public static void main(String[] args) {
new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("r1 -> i=" + i);
}
}
}.run();
new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("r2 -> i=" + i);
}
}
}.run();
}
}
复制代码 代码很简单,每个线程依次输出0-4这5个数字,运行结果:
r1 -> i=0 r1 -> i=1 r1 -> i=2 r1 -> i=3 r1 -> i=4 r2 -> i=0 r2 -> i=1 r2 -> i=2 r2 -> i=3 r2 -> i=4
二、向Runnable传递参数
实际应用中,线程开始处理前,通常会有一些初始参数,如果要传入参数,可以参考下面的方法,先定义一个Runnable的子类
package com.cnblogs.yjmyzz;
public class MyRunnable implements Runnable{
private String name;
private int max;
public MyRunnable(String name,int max){
this.name = name;
this.max = max;
}
public void run() {
for (int i = 1; i <= max; i++) {
try {
Thread.sleep(5);
System.out.println(name + ".i=" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然后这样使用:
package com.cnblogs.yjmyzz;
public class RunnableDemo2 {
public static void main(String[] args) {
new MyRunnable("A", 5).run();
new MyRunnable("B", 5).run();
}
}
运行结果: A.i=1 A.i=2 A.i=3 A.i=4 A.i=5 B.i=1 B.i=2 B.i=3 B.i=4 B.i=5
三、利用Thread并行处理
刚才的二个例子,相当大家也发现了问题,虽然是有二个线程,但是始终是按顺序执行的,上一个线程处理完成前,下一个线程无法开始,这其实跟同步处理没啥二样,可以通过Thread类改变这种局面:
public class RunnableDemo3 {
public static void main(String[] args) {
Runnable r1 = new MyRunnable("A", 5);
Runnable r2 = new MyRunnable("B", 5);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
Thread通过start方法,可以让多个线程并行处理,运行结果如下:
B.i=1 A.i=1 B.i=2 A.i=2 B.i=3 A.i=3 B.i=4 A.i=4 B.i=5 A.i=5
从输出结果上看,二个线程已经在并行处理了。
四、通过在线抢购示例理解资源共享
双十一一过,每到这个时候,通常是狼多肉少,下面的OrderRunnable类模拟这种抢购情况,假设产品数只有10个,抢购的客户却有100个
package com.cnblogs.yjmyzz;
public class OrderRunnable implements Runnable{
String taskName;
public OrderRunnable(String taskName){
this.taskName=taskName;
}
private int productNum = 10;
private int customerNum = 100;
public void run() {
for (int i = 0; i < customerNum; i++) {
if (productNum > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(taskName + " -> order success!");
productNum -= 1;
}
}
}
}
现在想用二个线程来处理:
package com.cnblogs.yjmyzz;
public class RunnableDemo4 {
public static void main(String[] args) {
Runnable r1 = new OrderRunnable("A");
Runnable r2 = new OrderRunnable("B");
new Thread(r1).start();
new Thread(r2).start();
}
}
运行结果:
A -> order success! B -> order success! B -> order success! A -> order success! B -> order success! A -> order success! A -> order success! B -> order success! B -> order success! A -> order success! B -> order success! A -> order success! A -> order success! B -> order success! A -> order success! B -> order success! A -> order success! B -> order success! A -> order success! B -> order success!
显然,这个结果不正确,只有10个产品,却生成了20个订单! 正确的做法,让多个Thread共同使用一个Runnable:
package com.cnblogs.yjmyzz;
public class RunnableDemo5 {
public static void main(String[] args) {
Runnable r1 = new OrderRunnable("A");
new Thread(r1).start();
new Thread(r1).start();
}
}
执行的结果如下: A -> order success! A -> order success! A -> order success! A -> order success! A -> order success! A -> order success! A -> order success! A -> order success! A -> order success! A -> order success! A -> order success!
五、ThreadPoolExecutor
如果有大量线程,建议使用线程池管理,下面是ThreadPoolExecutor的示例用法:
package com.cnblogs.yjmyzz;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RunnableDemo7 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 10, 1,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));
for (int i = 0; i < 6; i++) {
threadPool.execute(new MyRunnable("R"+i, 5));
}
}
}
ThreadPoolExecutor几点使用建议 ThreadPoolExecutor
六、ThreadPoolTaskExecutor
终于轮到我大Spring出场了,Spring框架提供了org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor类,可以用注入的形式生成线程池
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-autowire="byName">
<bean id="threadPoolTaskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="2" />
<property name="maxPoolSize" value="10" />
<property name="queueCapacity" value="1000" />
<property name="keepAliveSeconds" value="15" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>
</beans>
配置好以后,就可以直接使用了
package com.cnblogs.yjmyzz;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
public class RunnableDemo8 {
@SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"spring.xml");
ThreadPoolTaskExecutor taskExecutor = applicationContext.getBean(
"threadPoolTaskExecutor", ThreadPoolTaskExecutor.class);
for (int i = 0; i < 6; i++) {
taskExecutor.execute(new MyRunnable("R" + i, 5));
}
}
}
七、FutureTask
如果某些线程的处理非常耗时,不希望它阻塞其它线程,可以考虑使用FutureTask,正如字面意义一样,该线程启用后,马上开始,但是处理结果将在”未来”某一时刻,才真正需要,在此之前,其它线程可以继续处理自己的事情
package com.cnblogs.yjmyzz;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class RunnableDemo9 {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
FutureTask<String> task = new FutureTask<String>(
new Callable<String>() {
public String call() throws InterruptedException {
System.out.println("FutureTask开始处理...");
Thread.sleep(1000);
return "hello world";
}
});
System.out.println("FutureTask准备开始...");
new Thread(task).run();
System.out.println("其它处理开始...");
Thread.sleep(1000);
System.out.println("其它处理完成...");
System.out.println("FutureTask处理结果:" + task.get());
System.out.println("全部处理完成");
}
}
二个注意点:
a) FutureTask使用Callable接口取得返回值,因为结果可能并不需要立刻返回,而是等到未来真正需要的时候,而Runnable并不提供返回值
b) FutureTask通过Thread的run()或start()调用后,马上就开始处理,但并不阻塞后面的线程,在真正需要处理结果的时候,调用get()方法,这时如果FutureTask本身的处理尚未完成,才会阻塞,等待处理完成
刚才的运行结果:
FutureTask准备开始...
FutureTask开始处理...
其它处理开始...
其它处理完成...
FutureTask处理结果:hello world
全部处理完成
可以看到,“其它处理”并未被FutureTask阻塞,但FutureTask其实已经在后台处理了。
总结:
在我们刚接触的时候可能会迷糊继承Thread类和实现Runnable接口实现多线程,其实在接触后我们会发现这完全是两个不同的实现多线程,一个是多个线程分别完成自己的任务,一个是多个线程共同完成一个任务。 其实在实现一个任务用多个线程来做也可以用继承Thread类来实现只是比较麻烦,一般我们用实现Runnable接口来实现,简洁明了。 大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类(Thread)创建子类。
参考传送门-菩提树下的杨过 Java中继承thread类与实现Runnable接口的区别
- python3 下调用zabbix api 获取多个机房的IP
- TensorFlow从0到1 - 14 - 交叉熵损失函数——防止学习缓慢
- 用优雅的配色来缔造图表专业主义~
- python文件名与包名冲突
- python3 Zabbix监控-api的使用-python
- 安装python虚拟环境
- 各种排序算法的分析及java&python实现
- python3elk相关---根据Elasticsearch(es)日志处理,生成excel并定时邮箱发送附件
- python数据科学-数据预处理
- python3 邮箱相关邮箱的服务
- 谈一谈Python中str()和repr()的区别
- TensorFlow从0到1 - 15 - 重新思考神经网络初始化
- python2.7搬运--->TensorFlow - 深度学习破解验证码
- 来学习几个简单的Hive函数啦
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- RabbitMQ集群搭建过程
- java中的reference(一): GC与4种基本的Reference(强软弱虚)
- java中的reference(三): FinalReference和Finalizer的源码分析
- 【ceph】性能测试工具cosbench(1)
- linux命令tree的使用
- 腾讯云V3签名方法之iOS
- Go语言学习之 panic 和 recover
- Go语言学习之函数
- 【Spark Operator】webhook的分析
- 图解人脸识别算法facenet系列(一)
- Go 语言学习之 struct
- Go 语言学习之map
- 前端|利用手机号登录获取手机验证码
- Linux netstat命令结果分析
- setlistmap部分源码解析