Java面试题系列——JavaSE面试题(线程一)

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博客

ThreadLocal类(线程本地存储)详解_饭一碗的博客-CSDN博客

ThreadLocal的使用方法、作用、使用场景和原理 - 简书 (jianshu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小海海不怕困难

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值