![](https://i-blog.csdnimg.cn/blog_migrate/c70a891ae296c24d75cbf85cc00b477d.jpeg)
个人中心:SueWakeup
系列专栏: 学习Java
个性签名:保留赤子之心也许是种幸运吧
![](https://i-blog.csdnimg.cn/blog_migrate/e71a6eeae76de4a20d9304ad6b70965c.jpeg)
目录
注:手机端浏览本文章可能会出现 “目录”无法有效展示的情况,请谅解,点击侧栏目录进行跳转
前言
1. 什么是多线程?
答:首先,我们在使用软件程序时,计算机会以“进程”的方式运行程序,并分配“CPU”、“网络”、“磁盘”、“内存”等资源。一个进程中包含多个线程,多线程是多任务的一种特别形式。
2. 为什么使用多线程?
答:多线程使用了更小的资源开销,能满足程序员编写高效率的程序充分利用CPU
3. 多线程用在哪些地方?
答:程序的并发执行
线程的创建和实现
1. 线程的创建
1.1 创建 Thread 对象
示例:
Thread a =new Thread();
源码:
- 通过new Thread()创建Thread对象
解读:
如上图所示,通过Thread的构造方法创建Thread对象,在()中未使用字符串定义线程名称时会自动生成形式为“Thread-线程序号”的名称,如下图所示:
1.2 通过线程池创建管理线程
1.2.1 固定大小的线程池
//Executors : 工具类,用于创建各种形式(类型)的线程池
//Executors.newFixedThreadPool() : 创建固定线程数目的线程池
//线程池 : 创建5个线程池,在任务执行时,其他子线程等待,不完成自己工作之外的事
ExecutorService executorService = Executors.newFixedThreadPool(5);
int total =0;
for(int i=1;i<=10000;i+=2000){
//向线程池提交一个线程任务(Callable接口实现类对象)
//线程池分配一个空闲线程执行该任务
Future<Integer> future = executorService.submit(new SubTask(i, i + 2000));
total += future.get();
}
System.out.println(total);
//关闭线程池
executorService.shutdown();
解读:
- 通过 Executors 工具类的 newFixedThreadPool() 方法创建固定大小的线程池
- 通过 ExecutorService 对象 executorService 的 submit() 方法向线程池提交任务,并将返回的 Future 对象保存起来
- 使用 Future 对象 future 的 get() 方法获取每个任务的执行结果,并将结果累加到 total 变量中
- 通过 executorService 的 shutdown() 方法关闭线程池
1.2.2 非固定大小的线程池
/*固定线程池和非固定线程池
固定线程池限制线程数目,需要等待限制数目个数的线程池完成任务后加入线程
*/
ExecutorService fixedThreadPool= Executors.newFixedThreadPool(10);
//非固定线程池,适用于服务器计算机性能较好的系统,可以做到随应随处理
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//向线程池提交一个线程任务(Callable接口实现类对象)
//线程池分配一个空闲线程执行该任务
while (true){
//每次提交1个线程任务
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
解读:
- 通过 Executors 工具类的 newCachedThreadPool() 方法创建一个可缓存线程池 cachedThreadPool
- cachedThreadPool 可以动态创建线程来处理任务,如果有空闲线程就会被重复利用,如果没有则会动态创建新线程。适用于任务相对较轻量的情况
2. JVM中线程的查看
示例:
//jvm中的线程
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos){
System.out.printf("[%d]%s\n",threadInfo.getThreadId(),threadInfo.getThreadName());
}
解读:
- 通过ManagementFactory.getThreadMXBean()方法获取一个ThreadMXBean实例,用于访问系统程序的管理接口
- 调用threadMXBean.dumpAllThreads(false,false)方法获取当前所有线程的信息,返回一个ThreadInfo数组;dumpAllThread(false,false)方法的两个参数分别表示是否获取同步的堆栈跟踪信息和锁定的监视器信息
- 遍历threadInfos数组,对每个ThreadInfo对象调用getThreadId()和getThreadName()方法获取线程的ID和名称,最后以“[线程ID]线程名称”的格式在控制台上打印出来。
3.线程的实现
①通过实现Runnable接口
示例:
public static void main(String[] args) {
//子线程
Thread a =new Thread(new Runnable() {
@Override
public void run() {
//演示代码,在这个线程中向控制台打印a-z之间的小写字母
for(char b='a';b<'z';b++){
System.out.println(Thread.currentThread().getName()+":"+b);
}
}
},"子线程1");
a.start();//启动子线程,自动执行Runnable接口实现类对象的run()
//main主线程
for(char c='A';c<'Z';c++){
System.out.println(Thread.currentThread().getName()+":"+c);
}
}
源码:
解读:
如上图所示为Thread的构造方法之一,根据源码所知,Thread(Runnable target,String name)有两个参数,第一个参数是我们实现的Runnable接口,第二个参数是可被我们定义的线程名称,可省略不写,不写时会返回jvm自动分配的名称,如下图所示
未定义线程名称时,线程的名称:
- 在new Thread()中使用new Runnable()实现Runnable接口
- 实现接口后需要重写Runnable接口的run方法
- 在run方法中我们可以定义在这个线程中实现的逻辑
演示的代码中
- a.start()中,start()方法是启动线程的方法,自动执行Runnable接口实现类对象的run()方法
- Thread.currentThread.getName()是获取线程名称的方法,返回值为String类型
②通过继承Thread类本身
示例:
public static void main(String[] args) {
//创建方式二:通过继承Thread类来创建进程
Thread d =new Thread("子线程2"){
@Override
public void run() {
for(int i=1;i<27;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
};
d.start();
//main主线程
for(char c='A';c<'Z';c++){
System.out.println(Thread.currentThread().getName()+":"+c);
}
}
源码:
解读:
如上图所示为Thread的构造方法之一
- 在new Thread()后使用{...}代码块创建线程,以达到继承Thread类的目的
- 可以在new Thread()中定义字符串类型的线程名称,演示代码中的线程名称可不写
上图源码为定义了线程名称的Thread类的构造函数,所以会出现@NotNull
③实现Callable接口和FutureTask对象
示例:使用Callable接口和FutureTask对象求1-10000的所有数的和
public static void main(String[] args) throws ExecutionException, InterruptedException {
//多线程运行程序
//1.Callable接口的实现类,封装线程执行逻辑,并返回执行结果
SubTask subTask1 = new SubTask(1, 2000);
SubTask subTask2 = new SubTask(2000, 4000);
SubTask subTask3 = new SubTask(4000, 6000);
SubTask subTask4 = new SubTask(6000, 8000);
SubTask subTask5 = new SubTask(8000, 10001);
//2.Callable接口的实现类对象 ==> FutureTask对象(相当于Runnable接口实现类对象)
FutureTask<Integer> futureTask1 = new FutureTask<>(subTask1);
FutureTask<Integer> futureTask2 = new FutureTask<>(subTask2);
FutureTask<Integer> futureTask3 = new FutureTask<>(subTask3);
FutureTask<Integer> futureTask4 = new FutureTask<>(subTask4);
FutureTask<Integer> futureTask5 = new FutureTask<>(subTask5);
//3.创建线程
Thread t1 = new Thread(futureTask1);//1-2000
Thread t2 = new Thread(futureTask2);//2000-4000
Thread t3 = new Thread(futureTask3);//4000-6000
Thread t4 = new Thread(futureTask4);//6000-8000
Thread t5 = new Thread(futureTask5);
//4.启动5个子线程后,自动执行子线程的任务
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
//5.当所有子线程执行结束后
Integer sum1 = futureTask1.get(); //返回Callable接口实现类对象的call()方法返回值
Integer sum2 = futureTask2.get();
Integer sum3 = futureTask3.get();
Integer sum4 = futureTask4.get();
Integer sum5 = futureTask5.get();
int total =sum1+sum2+sum3+sum4+sum5;
System.out.println(total);
}
//计算指定范围内的数字累加和
class SubTask implements Callable<Integer>{
private int begin,end;
public SubTask(int begin,int end){
this.begin = begin;
this.end = end;
}
@Override
public Integer call() throws Exception {
int sum =0;
for(int i=begin;i<end;i++){
sum +=i;
}
return sum;
}
}
源码:
解读:
如上图源码所示,Callable与Runnable近似,但是Runnable不返回结果,也不能抛出检查异常。
- 在示例代码中SubTask类实现了Callable接口,在Callable<V>中定义返回值类型,V为基础数据类型的包装类,并在定义的类中重写call()方法,返回值同V。
- 使用new FutureTask()创建Callable接口的实现对象
- 在new Thread()中加载futureTask对象
- 使用futureTask对象的get()方法获取返回值
实现线程的三种方式的对比
- 使用 实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类
- 使用 继承 Thread 类的方式创建多线程时,编写简单,但如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程