java 多线程实现的三种方式区别
前言:
java多线程其实在工作中接触的并不是很多,偶尔用一下,但是这个特性又是开发工程师走向大牛必须要掌握的知识点,所以花几天时间整理了一下,一方便梳理知识点,另一方面也是为了以后更好地使用。
一. 线程和进程
线程可以理解是一个程序中可以独立执行的模块,一个程序在一个时间段内同时做好几件事(起码表面看起来是的)就是多线程最明显的表征;
进程是一次计算机的执行活动,可以是整个程序也可以是部分程序的动态执行;
从概念上看,进程是包含线程的,一个进程至少包含一个线程。
区别:
1. 系统资源管理区别
进程是在操作系统上运行的,有独立的地址空间;线程是运行在进程内部的,一个进程可以有多个线程;在一个进程内多线程可以交替切换,提高系统的并发度。
2. 通信行为区别
进程通过操作系统转发指令,是拥有独立资源的单位;线程不拥有系统资源,只能访问进程的资源;同一个进程的多个线程可以共享这个进程的所有资源;线程拥有自己的栈空间,拥有独立的执行序列。
3. 系统开销区别
创建进程时系统需要分配内存区域;在切换进程时需要保存当前进程的CPU环境并且要为被调度运行的进程设置CPU环境;线程创建不需要分配内存,使用的是所属的进程资源;切换线程时也只需要保存和设置少量寄存器的内容,不涉及存储器管理方面操作;线程消耗的资源远小于进程。
二、线程的生命周期
1. 线程状态
新建状态
可运行状态
运行状态
阻塞状态
结束状态
三、实现多线程的两个基础方法
1. 继承Thread重写run方法
public class MyThread extends Thread {
private int num;
private String threadName;
private long result;
public MyThread(int num, String threadName) {
this.threadName = threadName;
this.num = num;
}
public void run() {
for (int i = 0; i < num; i++) {
result += i;
}
}
public String getThreadName() {
return threadName;
}
public void setResult(long result) {
this.result = result;
}
public long getResult() {
return result;
}
}
2. 实现Runnable 接口重写run方法
public class MyRunnable implements Runnable {
private int num;
private String threadName;
private long result;
public MyRunnable(int num, String threadName) {
this.threadName = threadName;
this.num = num;
}
public void run() {
for (int i = 0; i < num; i++) {
result += i;
}
}
public String getThreadName() {
return threadName;
}
public void setResult(long result) {
this.result = result;
}
public long getResult() {
return result;
}
}
3. 测试两种方法
package thread;
public class Main {
public static void main(String[] args) {
threadTest();
// runnableTest();
}
private static void threadTest() {
MyThread myThread_1 = new MyThread(10, "thread_1");
MyThread myThread_2 = new MyThread(10000, "thread_2");
myThread_1.setResult(10);
myThread_1.start();
myThread_2.start();
do {
System.out.println("--------------------------------------------------");
System.out.println("thread name: " + myThread_1.getThreadName() + ", status: " + myThread_1.isAlive() + ",result: " + myThread_1.getResult());
System.out.println("thread name: " + myThread_2.getThreadName() + ", status: " + myThread_2.isAlive() + ",result: " + myThread_2.getResult());
} while (myThread_1.isAlive() || myThread_2.isAlive());
}
private static void runnableTest() {
MyRunnable myRunnable_1 = new MyRunnable(10, "runnable_1");
MyRunnable myRunnable_2 = new MyRunnable(10000, "runnable_2");
Thread thread_1 = new Thread(myRunnable_1);
Thread thread_2 = new Thread(myRunnable_2);
thread_1.start();
thread_2.start();
do {
System.out.println("--------------------------------------------------");
System.out.println("thread name: " + myRunnable_1.getThreadName() + ", status: " + thread_1.isAlive() + ",result: " + myRunnable_1.getResult());
System.out.println("thread name: " + myRunnable_2.getThreadName() + ", status: " + thread_2.isAlive() + ",result: " + myRunnable_2.getResult());
} while (thread_1.isAlive() || thread_2.isAlive());
}
}
4. 两种方法的比较
如果非要说区别,其实就是实现接口和继承的区别,看开发者的使用习惯
另外一点,两种方法的run都是不能传参数的,只能通过类的方法设置参数使用
四、实现Callable
接口重写call()方法实现多线程
上述两种基础方法的run都是void类型,想获取返回只能另加逻辑,而实现Callable接口重写call(的好处是允许call函数有返回,下面举例
import java.util.concurrent.Callable;
public class MyCall implements Callable<Long> {
private int num;
private String threadName;
private long result;
public MyCall(int num, String threadName) {
this.threadName = threadName;
this.num = num;
}
public Long call() throws Exception {
for (int i = 0; i < num; i++) {
result += i;
}
return result;
}
}
// 下面是测试
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
try {
callTest();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void callTest() throws ExecutionException, InterruptedException {
MyCall myCall_1 = new MyCall(10, "call_1");
MyCall myCall_2 = new MyCall(10000, "call_2");
FutureTask<Long> f1 = new FutureTask<Long>(myCall_1);
FutureTask<Long> f2 = new FutureTask<Long>(myCall_2);
Thread thread_1 = new Thread(f1);
Thread thread_2 = new Thread(f2);
thread_1.start();
thread_2.start();
System.out.println(f1.get()); // 获取返回
System.out.println(f2.get()); // 获取返回
}
}
五、线程池管理多线程
给一个简单的样例
import java.util.concurrent.Callable;
public class MyCall implements Callable<Long> {
private int num;
private String threadName;
private long result;
public MyCall(int num, String threadName) {
this.threadName = threadName;
this.num = num;
}
public Long call() throws Exception {
for (int i = 0; i < num; i++) {
result += i;
}
return result;
}
}
// 线程池方式测试
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCall myCall_1 = new MyCall(10, "call_1");
MyCall myCall_2 = new MyCall(10000, "call_2");
ExecutorService service = Executors.newFixedThreadPool(5);
Future<Long> f1 = service.submit(myCall_1);
Future<Long> f2 = service.submit(myCall_2);
System.out.println(f1.get()); // 获取返回
System.out.println(f2.get()); // 获取返回
service.shutdown();
}
}
六、四种方式总结
1. 继承Thread和实现Runnable使用起来比较接近,唯一区别就是Runnable避免了单一继承的缺点
2. 有返回的情况下建议使用Callable,而且可以抛出异常方便定位问题
3.线程池其实不算是实现方式(有些人会把这个也算是实现方式),它更像是一种管理多线程的方式
本文地址:https://blog.csdn.net/qingquanyingyue/article/details/112030797