今天给大家分享的是我Java学习中多线程的学习部分。
首先大家肯定都有一个问题:什么是多线程?为什么要使用多线程?在这里我就给大家讲解一下。而要说起多线程,就不得不说一下什么是线程和进程。
什么是进程、线程、多线程?
目前我们所熟知的操作系统Windows、Unixs、Linux系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。多线程就是指一个进程中同时有多个执行路径(线程)正在执行。
为什么要使用多线程(多线程的优点)?
- 进程之间不能共享内存,但线程之间共享内存非常容易。
- 系统创建进程是需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
- Java语言内置了多线程功能支持,而不是单纯的作为底层操作系统的调度方式,从而简化了Java的多线程编程。
继承Thread类来创建线程类
通过继承Thread类来创建并启动多线程分为以下几步:
-
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run方法称为线程执行体。
-
创建Thread子类的实例,即创建了线程对象。
-
调用线程对象的start()方法来启动该线程。
Test类:
package com.langsin.thread;
public class Test {
public static void main(String[] args) {
/*
*main方法:主线程,打印1-100的数字
*创建一个线程,打印1-100的数字
*/
ThreadDemo demo = new ThreadDemo("线程A"); //创建线程后,再启动线程,线程就单独去运行了
demo.start();
String threadName = Thread.currentThread().getName(); //获取当前运行线程,即main线程的名称
for(int i=1; i<=100; i++) {
System.out.println("当前线程:" + threadName + ",进行输出值" + i);
}
}
}
ThreadDemo类:
package com.langsin.thread;
public class ThreadDemo extends Thread {
public ThreadDemo(String name) {
this.setName(name);
}
public void run() {
for(int i=0; i<100; i++) {
System.out.println("当前线程:" + this.getName() + ",输出了数值:" + i);
}
}
}
下面是运行结果的部分截图,体现出了线程是抢占式运行。
在上述代码中,学习了线程的两个方法:
- Thread.currentThread():该方法返回当前正在执行的线程对象
- getName():返回对象线程的名称
实现Runnable接口创建类
实现Runnable接口,创建并启动多线程的步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的线程执行体
- 创建Runnable实现类的实例对象,并以此实例对象作为Thread的target来创建Thread类,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动该线程。
Test类:
package com.langsin.thread;
public class Test {
public static void main(String[] args) {
RunableDemo target = new RunableDemo(); //任务体
Thread thread = new Thread(target, "线程A");
thread.start();
String threadName = Thread.currentThread().getName(); //获取main线程的名称
for(int i=1; i<=100; i++) {
System.out.println("当前线程:" + threadName + ",进行输出值" + i);
}
}
}
RunableDemo类:
package com.langsin.thread;
public class RunableDemo implements Runnable {
public void run() {
//Runable实际上并不是创建一个线程类,只是一个线程的任务体
String name = Thread.currentThread().getName();
for(int i=0; i<100; i++) {
System.out.println("当前线程:" + name + ",输出了数值:" + i);
}
}
}
使用Callable和Future创建线程
创建并启动有返回值的线程的步骤如下:
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且该call()方法有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程结束后的返回值。
Test类:
package com.langsin.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws Exception {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
int a = 3;
int b = 5;
Thread.sleep(2000);
return a + b;
}
};
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
//通过task获取返回的结果值
Integer value = task.get(3,TimeUnit.SECONDS); //阻塞式方法,设置等待3秒后获取。若设置等待时间小于休息时间,则报错
System.out.println(value);
}
}
创建线程的三种方式的对比
通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常,Callable需要FutureTask来进行封装成Thread可识别的target目标。因此可以将实现Runnable接口和实现Callable接口归纳为一种方式。这种方式与继承Thread方式之间的主要差别如下:
采用Runnable、Callable接口的方式创建多线程:
- 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
- 多个线程可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
- 劣势:编程稍微复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
采用Thread类的方式创建多线程:
- 编写简单,如果需要访问当前线程,直接使用this即可。