4.多线程面试题--持续更新

目录

1.并行&并发

2.进程&线程

3.线程的调度

4.什么是主线程

5.Java中如何使用多线程

6.Thread对象可不可以调用run方法执行线程任务

7.Thread和Runnable区别

8.如何中断线程?

9.什么是守护线程?

10.线程安全问题产生原因?

11.如何解决线程安全问题?

12.显示锁和隐式锁区别(sync\lock)

13.公平锁和非公平锁

14.线程状态间如何转换

15.为什么使用线程池

16.几种线程池如何工作的

17.线程池底层原理

18.线程池工作原理

19.使用线程池好处

20.Lambda标准格式

21.为什么使用Lambda什么情况下使用?

1.并行&并发

  • 并发:指两个或多个事件在同一时间段内发生。(cpu交替执行任务)。
  • 并行:指两个或多个事件在同一时刻发生。(cpu同时执行任务,速度快)。

2.进程&线程

  • 进程是指一个内存中运行的应用程序,每一个进程都有一个独立的内存空间。
  • 线程就是应用程序通向cpu的一条路径。每个线程都会开辟一个栈内存执行run。

3.线程的调度

4.什么是主线程

  • 主线程:即执行主方法(main)的线程。
  • JVM执行main方法,main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执行main方法,

5.Java中如何使用多线程

  • 继承Thread
  • 实现Runnable接口

6.Thread对象可不可以调用run方法执行线程任务

  • 多线程程序的内存:每个线程拥有独立栈空间,共用一个堆内存。
  • 首先程序的执行入口是main方法,jvm将main方法压栈执行,开启main线程。
  • 压入栈后先创建Thread类的子类对象。
  • 若是直接线程对象mt调用run方法的话会把run方法压入栈中执行,这就变成了单线程程序
  • 通过子类对象调用start 方法,此时就会开辟新的栈空间并在该空间中执行run方法,即此时run方法就不是在主方法所在的栈空间执行。若在主方法中再创建一个Thread类的子类对象并调用start方法,则又会开辟一个栈空间来执行run方法。
  • 对于cpu来说,有三个栈内存供其选择,cpu可以执行main方法,也可以执行两个run方法,高速交替执行。

7.Thread和Runnable区别

  • 1.避免了单继承的局限性:
    • 一个类只能继承一个类,类继承了Thread类就不能继承其它类。而实现Runnable接口的类,还可以继承其它的类,实现其它的接口
  • 2.增强了程序的扩展性,降低了程序的耦合性(解耦):
    • 实现Runnable接口的方式创建多线程,把设置线程任务和开启新线程进行了分离(解耦),传递不同的实现类对象实现不同的任务。
  • 3.线程池技术,接收Runnable类型任务,不接收Thread类型线程,线程池管理的是任务。
  • 4.实现Runnable接口方式创建多线程更适合多个线程执行一个线程任务

8.如何中断线程?

  • 若当主线程执行完,子线程t1还没执行完,想要中断子线程,可以给t1添加中断标记,即t1.intertupt();当子线程执行以下方法时会触发InterruptException,比如在sleep休眠时,t1会检查自身是否携带中断标记,有的话就会new一个异常抛出被catch捕获,进入到catch块中,至于要不要t1死亡就看程序员在catch块中怎么写的,若要线程t1死亡,直接return结束掉run线程任务即可。
  • 由外部干涉一个完整执行流程死亡不合理,所以通过打中断标记,线程触发标记进入catch块中

9.什么是守护线程?

  • 线程:分为守护线程和用户线程
  • 用户线程:直接创建的线程,当一个进程中不包含任何存活的用户线程,则进程结束。
  • 守护线程:守护用户线程的,当最后一个用户线程结束,所有守护线程自动死亡。
  • 设置线程为守护线程: 在启动子线程前设置t1.setDaemon(true)

10.线程安全问题产生原因?

  • 多线程访问了共享的数据,会产生线程安全问题。

11.如何解决线程安全问题?

  • 同步代码块,锁对象为任意对象。
  • 同步方法,锁对象为this。
  • 静态同步方法,锁对象为本类class属性,即类.class
  • Lock锁,加锁与释放锁方法化

12.显示锁和隐式锁区别(sync\lock)

Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?

显示锁lock,隐式锁Synchronized

出身

  • sync:java关键字,由JVM维护,是JVM层面的锁
  • lock:JDK5后出现的类,是API层面的锁,调用对应API。
  • sync是底层是通过monitorenter进行加锁,通过monitorexit来退出锁的。
  • lock是通过调用对应的API方法来获取锁和释放锁的。

使用方式

  • sync:隐式锁,由系统维护,自动让程序释放占用的锁,如果非逻辑问题,是不会出现死锁
  • lock:显示锁,需要手动写代码去获取锁和释放锁,如果没有释放锁,就有可能导致出现死锁

等待中断

  • sync:不可中断
  • lock:可中断,中断方式:调用设置超时方法tryLock(long timeout ,timeUnit unit)或调用interrupt()方法可以中断。

公平/非公平锁

  • sync:非公平锁
  • lock:都可,默认非公平锁,构造方法传boolean。true为公平锁,false为非公平锁。

性能

悲观乐观锁

13.公平锁和非公平锁

  • 公平锁:线程先来后到,排队执行。lock子类ReentrantLock创建对象,构造方法传参true则为公平锁,否则默认非公平锁
  • 非公平锁:线程不排队,一起抢夺cpu资源。synchrnized就是非公平锁。

14.线程状态间如何转换

我们不需要去研究这几种状态的实现原理,我们只需要知道在做线程操作中存在这样的状态并且理解,新建与被终止还是很容易理解的,下面就研究一下线程从Runnable(可运行)状态与非运行状态之间转换问题。

  • 当线程被创建并启动以后,它不是一启动就进入执行状态,也不是一直处于执行状态。在线程的生命周期中,有6种线程状态,在Thread类的内部类(嵌套类)中描述了线程的状态。

线程状态的描述

  • 首先NEW(新建)状态就是new Thread()或new Thread子类()创建时,即new出这两个对象(线程)时。
  • 然后RUNNABLE(运行)状态,当new出来多个线程时会一起抢夺CPU的执行权,谁抢到谁就处于运行状态,
  • 而没有抢到的线程则处于BLOCKED(阻塞)状态,线程的阻塞状态和执行状态可以互相转换,这主要取决于是否抢到cpu;
  • 当线程执行完run()方法或者调用stop()方法或者线程产生了异常,线程就会进入TERMINATED(死亡)状态
  • 若调用Thread.sleep(毫秒)方法或者Object.wait(毫秒)方法时,程序就会进入到TIMED_WAITING休眠(睡眠)状态也称计时等待,当线程睡醒后,若是cpu空闲,则该线程就会抢夺cpu进入运行状态,若cpu正被别的线程使用,则睡醒线程则进入阻塞状态。
  • 当线程中调用Object.wait(不设置时间,空参),线程就会进入WAITING即无限(永久)等待状态,可以使用Object.notify()方法将等待状态的线程唤醒。
  • 睡眠状态和等待状态是有所区别的,前者是自己唤醒的,而后者需调用方法来唤醒,二者称冻结状态
  • 阻塞状态与睡眠、等待状态有所区别,前者是具有cpu的执行资格,等待cpu空闲时执行,后者放弃cpu的执行资格,cpu空闲也不执行。

15.为什么使用线程池

  • 我们使用线程的时候就去创建一个线程,这样实现起来非常简单,
  • 但是就会出现一个问题:如果并发的线程数量很多,并且每一个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
  • 有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是继续执行其它任务,在java中可以通过线程池来表达这样的效果。

16.几种线程池如何工作的

任务加入后各个线程池执行流程:

缓存线程池

  • 1.判断线程池是否存在空闲线程,存在则使用,不存在则创建线程放入线程池再使用。
  • 2.长度无限制。

定长线程池

  • 判断线程池是否存在空闲线程,存在则使用
  • 不存在空闲线程,且线程池未满,则创建线程放入线程池,再使用。
  • 不存在空闲线程,且线程池已满,则等待出现空闲线程再使用
  • 线程池线程长度是指定的数值。

单线程线程池

  • 判断线程池的那个线程是否空闲,空闲则使用,不空闲则等待这个线程空闲再用。
  • 池中只有一个线程,相当于获取定长线程池传参1效果。

周期定长线程池

  • 判断线程池是否存在空闲线程,存在则使用。
  • 不存在空闲线程,且线程池未满,则创建线程放入线程池,然后使用。
  • 不存在空闲线程,且线程池已满,则等待线程池存在空闲线程。
  • 实现定时执行或定时周期执行任务,当某个时机触发时,自动执行某个任务。

17.线程池底层原理

  • 线程池可以理解为容器(集合),一般使用集合(ArrayList,HashSet,LinkedList,HashMap)来充当容器,
  • 常用LinkedList<Thread>集合作为容器,泛型就是线程即集合中存放的都是线程。下面是在LinkedList集合中通过add方法向集合中添加一些线程即add(new Thread(xxx))。
  • 1.当程序第一次启动时,创建多个线程,保存到一个集合中。
  • 2.当我们想要使用线程的时候,就可以从集合中取出线程使用。
    •  Thread t=list.remove(0):从List集合中移除第0个线程(线程只能被一个任务使用)
    • Thread t=linked.removeFirst():从LikedList集合中移除第1个线程
  • 3.当我们使用完线程,需要将线程归还给线程池:即将线程再添加到集合中,(这里面有队列机制,从对头取出,队尾归还)
    • 若是List集合,则list.add(t);将线程归还到list集合末尾
    • 若是LinkedList集合,则linked.addLast(t);将线程归还到LinkedList集合尾端。

注意:

  • JDK1.5之后,JDK内置了线程池,我们可以直接使用。

18.线程池工作原理

线程池:

  • 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

工作原理:

  • 由于线程池中很多操作都是与优化资源相关,这里就通过一张图来了解线程池工作原理:
  • 项目的任务队列要执行1-5任务,执行任务时从线程池获取一个线程对象来执行任务,当要执行任务4和任务5时,线程池中已经没有空闲线程,此时任务等待执行,当其它某个任务执行完毕后归还线程到线程池后,再从线程池中获取线程,执行任务。

19.使用线程池好处

  • 1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 2.提高响应速度。当任务到达时,任务不需要等到线程创建就能立即执行。
  • 3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器搞垮(每个线程需要大约1MB,线程开的越多,消耗的内存也就越大)

20.Lambda标准格式

  • Lambda省去面向对象的条条框框,格式由3个部分组成:一些参数,一个箭头,一段代码
  • Lambda表达式的标准格式为:(参数类型 参数名称)->{代码语句}

格式说明:

  •   1.小括号代表接口中抽象方法(run)的参数列表,无参数则代表不需要任何条件;多个参数则逗号分离
  •   2.箭头代表将前面的参数传递给后面的方法体
  •   3.大括号内即为业务逻辑代码(重写接口中的抽象方法)

21.为什么使用Lambda什么情况下使用?

  • 要想使用接口中抽象方法,就需要实现这个接口并重写接口方法,
  • 为了避免创建接口实现类,采用匿名内部类方式虽然不再需要创建接口实现类,但还是需要每次重写接口中的抽象方法,
  • 为了更加简便,就采用Lambda方法,这样既不用创建接口实现类,也不用重写接口中抽象方法,只需要定义代码块(业务逻辑代码)

22.线程生命周期

  • 5步:新生,就绪,运行,阻塞,死亡
  • 7步:新生,就绪,运行,等待,锁定,阻塞,死亡。
  • 见14题

23.线程和集合的关系

 

 

24.线程通信常用哪些方法,说说生产者与消费者之间通信

  • wait(),notify,notifyAll()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值