1、进程与线程的区别
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
(1)地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。(2)资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。(3)健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。(4)执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。(5)切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。(6)其他:线程是处理器调度的基本单位,但是进程不是。
2、为什么要用多线程?
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
3、多线程的创建方式有哪些?
1、继承Thread
类
- 自定义线程类继承
Thread
类 - 重写
run()
方法,编写线程执行体(当成main()
方法用) - 创建线程对象,调用
start()
方法启动线程
//主方法
public class Demo01 {
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
thread2.start();
}
}
//100以内的偶数
class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
//100以内的奇数
class Thread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2!=0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
2、实现Runnable
接口
- 创建一个实现了
Runnable
接口的类 - 实现类去实现
Runnable
接口中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到
Thread
类的构造器中,创建Thread
类的对象 - 通过
Thread
类的对象调用start()
- 这里的
start()
首先启动了当前的线程,然后调用了Runnable
类型的target的run()
public class Demo02 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是子线程:" + i);
}
}
public static void main(String[] args) {
// runable的实现类型
Demo02 demo02 = new Demo02();
// 创建 线程对象
Thread t1 = new Thread(demo02);
t1.start();
for (int i = 101; i < 200; i++) {
System.out.println("=========>Demo02.main"+ i);
}
}
}
3、实现callable
接口
- 实现 Callable 接口
- 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
//2.接收线程运算后的结果
try {
Integer sum = result.get(); //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
4、是继承Thread类好,还是实现Runnable接口好?
实现runnable接口更好。原因:(1)代码架构上来说,具体执行的任务应该是和线程类(线程创建和运行机制)是解耦的。(2)继承thread类的形式,每次新建一个任务都需要创建一个新的独立线程->运行->销毁,性能损耗较大,实现runnable接口的形式可以调用线程池等实现线程复用,减少线程的创建和销毁带来的损耗。(3)继承Thread类后,由于java不支持多继承,限制了该类的可扩展性。本质对比:继承thread类,覆盖了整个run方法,传入runnable对象,执行他的run方法。
5、ThreadLocal类是什么类?
ThreadLocal被称为线程本地存储,顾名思义就将共享的数据存储到每个线程本地,这样每个线程拥有的都是该共享数据的副本,以此来限制共享数据的可见范围或可变性。使用ThreadLocal不需要锁也实现了线程安全且效率比互斥同步高,某些情况下也能避免类方法参数层层传递的缺点。比如spring框架中的RequestContextHolder类(每个请求中的request由于线程隔离了,信息都不一样)就使用ThreadLocal来实现的。
使用方法:
ThreadLocal<String> threadLocalOld = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return new String("dsadsa");
}
};
Thread thread = new Thread(()->{
String s = threadLocalOld.get();//每个线程调度get函数获取本线程的副本。
// do sth
threadLocalOld.set("dsad");// set函数set的值,只会设置本线程的值,不会对其他线程有任何影响。
});
Thread thread1 = new Thread(()->{
String s = new String("dsadsa");//ThreadLocal效果定义local variable类似。
// do sth
});
使用注意事项:
ThreadLocal.ThreadLocalMap.Entry中的key是弱引用的,也即是当某个ThreadLocal对象不存在强引用时,就会被GC回收,但是value是基于强引用的,所以当key被回收,但是value还存在其他强引用时,就会出现内存的泄露情况,在最新的ThreadLocal中已经做出了修改,即在调用set、get、remove方法时,会清除key为null的Entry,但是如果不调用这些方法,仍然还是会出现内存泄漏 :),所以要养成用完ThreadLocal对象之后及时remove的习惯。
6、你项目中的哪些地方使用到多线程?
1、一个业务逻辑有很多次的循环,每次循环之间没有影响,比如验证1万条url路径是否存在,正常情况要循环1万次,逐个去验证每一条URL,这样效率会很低,假设验证一条需要1分钟,总共就需要1万分钟,有点恐怖。这时可以用多线程,将1万条URL分成50等份,开50个线程,没个线程只需验证200条,这样所有的线程执行完是远小于1万分钟的。
2、订单处理(用户下单后可能支付状态不明确,我们后台可以通过多线程去主动核实第三方支付状态,来更新我们系统的订单状态)。
3、你有很多商品上架了,然后商品很多,你可以去使用线程池进行上架操作,使用多线程操作。
4、需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整型属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户。
持续更新中,敬请期待!
参考文章:
为什么要使用多线程 - ssssdy - 博客园 (cnblogs.com)
面试必考 | 进程和线程的区别 - 知乎 (zhihu.com)
多线程的几种创建方式_xingsfdz的博客-CSDN博客_多线程的创建方式
Java多线程中怎么创建线程 三种创建方式详解 | w3c笔记 (w3cschool.cn)
Java多线程之Callable接口的实现 - jason.bai - 博客园 (cnblogs.com)
创建线程,实现runnable接口还是继承Thread类好_大洼X的博客-CSDN博客