Java基础常见面试题——多线程

All Love In Java 相关链接:

Java基础常见面试题——入门:https://blog.csdn.net/qq_41822345/article/details/104488442
Java基础常见面试题——集合篇:https://blog.csdn.net/qq_41822345/article/details/104514994
Java基础常见面试题——HashMap总结:https://blog.csdn.net/qq_41822345/article/details/104436909
Java基础常见面试题——多线程:https://blog.csdn.net/qq_41822345/article/details/104488481
Java基础常见面试题——锁:https://blog.csdn.net/qq_41822345/article/details/104514840
Java基础常见面试题——最后十题:https://blog.csdn.net/qq_41822345/article/details/104480830

2020级应届生——Java面试题(for 秋招):https://blog.csdn.net/qq_41822345/article/details/100141708

Java基础常见面试题——设计模式:https://blog.csdn.net/qq_41822345/article/details/104488560
Java泛型(连面三家公司都问这个?):https://blog.csdn.net/qq_41822345/article/details/104967036
字符串String学习(常见笔试面试题):https://blog.csdn.net/qq_41822345/article/details/105070605
Java 数的位运算:https://blog.csdn.net/qq_41822345/article/details/104831622
日期API解析总结(Java8新日期类API):https://blog.csdn.net/qq_41822345/article/details/109881663

Java 并发编程学习笔记:https://blog.csdn.net/qq_41822345/article/details/104620428
Java 并发编程常见面试题:https://blog.csdn.net/qq_41822345/article/details/104640761
Java 内存模型:https://blog.csdn.net/qq_41822345/article/details/104617364

Java关键字——synchronized:https://blog.csdn.net/qq_41822345/article/details/105144315
Java关键字——volatile:https://blog.csdn.net/qq_41822345/article/details/105243644
Java关键字——ThreadLocal:https://blog.csdn.net/qq_41822345/article/details/105297940

JVM常见面试题1:https://blog.csdn.net/qq_41822345/article/details/104417108
JVM常见面试题2:https://blog.csdn.net/qq_41822345/article/details/104531570
JVM常见面试题3:https://blog.csdn.net/qq_41822345/article/details/104531640
Java JVM操作工具:https://blog.csdn.net/qq_41822345/article/details/119705517
JDK自带工具包的使用:https://blog.csdn.net/qq_41822345/article/details/105523832

java框架入门:https://blog.csdn.net/qq_41822345/article/details/104488132
java动态代理:https://blog.csdn.net/qq_41822345/article/details/105108955
MyBatis的实体类:https://blog.csdn.net/qq_41822345/article/details/104688219
Spring框架——IOC与AOP:https://blog.csdn.net/qq_41822345/article/details/104983468
Spring Boot 源码解析:https://blog.csdn.net/qq_41822345/article/details/104780423
SpringBoot——自动配置原理:https://blog.csdn.net/qq_41822345/article/details/108312465
如何优雅地停止 Spring Boot 应用:https://blog.csdn.net/qq_41822345/article/details/106800135
SpringBoot+Shiro(RBAC实现):https://blog.csdn.net/qq_41822345/article/details/107444270
SpringBoot+jasypt数据库连接加密:https://blog.csdn.net/qq_41822345/article/details/107435801
Swagger组件—Java最流行的API文档框架:https://blog.csdn.net/qq_41822345/article/details/106788392
单点登录(原理与代码):https://blog.csdn.net/qq_41822345/article/details/105015154

MyBatis学习笔记:https://blog.csdn.net/qq_41822345/article/details/104688219
MyBatis源码学习笔记(从设计模式看源码):https://blog.csdn.net/qq_41822345/article/details/104934044
SpringCloud微服务学习:https://blog.csdn.net/qq_41822345/article/details/104585350

后端开发——接口篇:https://blog.csdn.net/qq_41822345/article/details/108066285
后端开发——缓存篇:https://blog.csdn.net/qq_41822345/article/details/108110964
后端开发——日志篇:https://blog.csdn.net/qq_41822345/article/details/108120098

Java Web 学习笔记:https://blog.csdn.net/qq_41822345/article/details/104516659
什么是敏捷开发:https://blog.csdn.net/qq_41822345/article/details/106499258
前后端分离:https://blog.csdn.net/qq_41822345/article/details/104403807
java图书管理系统(前后端分离前):https://blog.csdn.net/qq_41822345/article/details/104403813
java图书管理系统(前后端分离后):https://blog.csdn.net/qq_41822345/article/details/104411133
微服务入门项目——外卖订单系统:https://blog.csdn.net/qq_41822345/article/details/104404451
Java项目——商城 (附源码):https://blog.csdn.net/qq_41822345/article/details/90289659
gulimall商城2021:https://blog.csdn.net/qq_41822345/article/details/114177833

1.为什么要用多线程?

  以前我认为多线程的作用就是提升性能。实际上,多线程并不一定能提升性能(甚至还会降低性能);多线程也不只是为了提升性能。
  多线程主要有以下的应用场景:
1、避免阻塞(异步调用)
  单个线程中的程序,是顺序执行的。如果前面的操作发生了阻塞,那么就会影响到后面的操作。这时候可以采用多线程,我感觉就等于是异步调用。这样的例子有很多: eg.ajax调用,就是浏览器会启一个新的线程,不阻塞当前页面的正常操作。
2、避免CPU空转
  以http server为例,如果只用单线程响应HTTP请求,即处理完一条请求,再处理下一条请求的话,CPU会存在大量的闲置时间 。因为处理一条请求,经常涉及到RPC、数据库访问、磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应的时候,CPU却不能去处理新的请求,因此http server的性能就很差,所以很多web容器,都采用对每个请求创建新线程来响应的方式实现,这样在等待请求A的IO操作的等待时间里,就可以去继续处理请求B,对并发的响应性就好了很多 。
3、提升性能
  在满足条件的前提下,多线程确实能提升性能。
  第1,任务具有并发性,也就是可以拆分成多个子任务。并不是什么任务都能拆分的,条件还比较苛刻 :子任务之间不能有先后顺序的依赖,必须是允许并行的。
  第2,只有在CPU是性能瓶颈的情况下,多线程才能实现提升性能的目的。比如一段程序,瓶颈在于IO操作,那么把这个程序拆分到2个线程中执行,也是无法提升性能的 。
  第3,有点像废话,就是需要有多核CPU才行。否则的话,虽然拆分成了多个可并行的子任务,但是没有足够的CPU,还是只有一个CPU在多个线程中切换来切换去,不但达不到提升性能的效果,反而由于增加了额外的开销,而降低了性能。

2.进程与线程的区别

  进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程,它可以被看作一个程序或者应用。线程又叫做轻量级进程。
  Java 默认有几个线程?
  Java 默认有两个线程:Main 和 GC
线程与进程的区别归纳:
  进程是资源分配的最小单位,它使用独立的数据空间。
  线程是程序执行的最小单位,它共享进程的数据结构。
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信(IPC),线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。

3.线程的创建方式?线程的几种可用状态?

1、继承Thread类,重写run方法;
2、实现Runnable接口,重写run方法。
区别:实现Runnable接口的类需要用Thread类包装后才能调用start()方法。Start()方法会通过对系统底层的一系列操作创建出一个相应的线程,与当前线程并发执行,从而实现多线程。
  Java 无法操作底层的,只能通过调用本地方法(如下),C++ 编写的动态函数库,由 C++ 去操作底层启动线程,Java 只是间接调用(用start()方法来间接调用)。

private native void start0();

3、实现callable接口,重写call方法,相较于实现Runnable 接口的实现,方法可以有返回值,并且抛出异常。
4、使用实现了Executor接口的ThreadPoolExecutor来创建线程池。
  实现Runnable接口这种方式更受欢迎,因为:①可以避免单继承的局限性;②将线程任务单独分离出来封装成对象(面向对象);③资源共享。
   本节的实例代码请看文末:Java 并发编程学习笔记

在线程中常用的几个方法:
sleep():使当前线程进入阻塞转态,且不释放锁。
wait():使当前线程进入等待状态,会释放锁。
notify():随机唤醒一个 wait 线程。
notifyAll():唤醒所有 wait 线程。
join():main()方法会等待调用join()方法的子线程执行结束。(main()方法需要该子线程的运行结果时使用)
yield():当前线程让出cpu时间片,进入runnable状态。
interrupt():中断当前线程。
在这里插入图片描述

4.了解线程池不? 线程池的基本参数有哪些? 线程池是解决什么问题的?

  装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的复用。(为了减少在创建和销毁线程上所花的时间以及系统资源的开销)java.util.concurrent.ThreadPoolExecutor 类就是一个线程池。
四种常用线程池:
Executors.newFixedThreadPool(n)
Executors.newScheduledThreadPool(n);
Executors.newSingleThreadExecutor()
Executors.newCachedThreadPool()

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @description:             常用四种线程池
 * @author: Liu Wen
 * @create: 2020-03-14 21:42
 **/
public class ThreadPoolTest {
    public static void main(String[] args){
          //指定线程数,固定的个数
//        ExecutorService executorService = Executors.newFixedThreadPool(5);
          //单列线程池
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //线程池个数看电脑配置
//        ExecutorService executorService = Executors.newCachedThreadPool();
        //安排5个,但是也可以灵活变化
        ExecutorService executorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            executorService.execute(()->{
               System.out.println(Thread.currentThread().getName()+":"+ temp);
            });
        }
        executorService.shutdown();
    }
}

线程池的优点:线程池是为了减少在创建和销毁线程上所花的时间以及系统资源的开销。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。
线程池的基本参数:
①corePoolSize:线程池的核心大小,也可以理解为最小的线程池大小。
②maximumPoolSize:最大线程池大小。
③keepAliveTime:空余线程存活时间,指的是超过corePoolSize的空余线程达到多长时间才进行销毁。
④unit:销毁时间单位。
⑤workQueue:存储等待执行线程的工作队列。

ArrayBlockingQueue:基于数组的先进先出。
LinkedBlockingQueue:基于链表的先进先出。
SynchronousQueue:不会保存任务,而是直接新建一个线程来执行任务。
PriorityBlockQueue:具有优先级的队列。

⑥threadFactory:创建线程的工厂,一般用默认即可。
⑦handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。

AbortPolicy:抛出异常
DiscardPolicy:放弃任务,不抛出异常
DisccardOldestPolicy:尝试与队列最前面的任务去竞争,不抛出异常
CallerRunsPlicy:谁调用谁处理7 大参数

  如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队;如果运行的线程等于或者多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不是添加新线程;如果无法将请求加入队列,即队列已经满了,则创建新的线程,除非创建此线程超出 maxinumPoolSize,在这种情况下,任务将被拒绝。
附:数据库连接池的优点:
第一:数据库连接不需要每次都去创建或销毁,所以更加节约资源,响应更快。
第二:数据库个数限定,不会出现数据库连接过多导致系统运行缓慢,更稳定。

5.你是怎么理解线程安全的?HashMap是线程安全的么?如果多个线程同时修改HashMap时会发生什么情况?

  线程安全:当多个线程访问某一个类(对象或方法)时,这个类始终能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。线程安全就是多线程访问时采用加锁机制(如:synchronized。)提供访问保护,当一个线程访问该类的某个数据时,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
  HashMap在多线程情况下,在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

6.多线程如何在多个CPU上分布?线程调度算法有哪些?线程调度和进程调度的区别?

一、cpu个数、核数、线程数的关系
cpu个数:是指物理上,也及硬件上的核心数;
核数:是逻辑上的,简单理解为逻辑上模拟出的核心数;
线程数:是同一时刻设备能并行执行的程序个数,线程数=cpu个数 * 核数【如果有超线程,再乘以超线程数】
二 、cpu线程数和Java多线程
首先明白几个概念:
(1) 单个cpu线程在同一时刻只能执行单一Java程序,也就是一个线程
(2) 单个线程同时只能在单个cpu线程中执行。真正在cpu处理机上运行的是线程。
(3) 线程是操作系统最小的调度单位,进程是资源(比如:内存)分配的最小单位
(4) Java中的所有线程在JVM进程中,CPU调度的是进程中的线程
(5) Java多线程并不是由于cpu线程数为多个才称为多线程,当Java线程数大于cpu线程数,操作系统使用时间片机制,采用线程调度算法,频繁的进行线程切换。
(6) 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
操作系统OS的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
a.那么java多进程,每个进程又多线程,cpu是如何调度的呢?
  个人理解:操作系统并不是单纯均匀的分配cpu执行不同的进程,因为线程是调度的最小单位,所以会根据不同进程中的线程个数进行时间分片,均匀的执行每个线程,也就是说A进程中有10个线程,而B进程中有2个线程,那么cpu分给进程的执行时间理论上应该是5:1才合理。
b.cpu线程数和java线程数有直接关系吗?
  个人理解:没有直接关系,正如上面所说,cpu采用分片机制执行线程,给每个线程划分很小的时间颗粒去执行,但是真正的项目中,一个程序要做很多的的操作,读写磁盘、数据逻辑处理、出于业务需求必要的休眠等等操作,当程序在进行I/O操作的时候,线程是阻塞的,线程由运行状态切换到等待状态,此时cpu会做上下文切换,以便处理其他的程序;当I/O操作完成后,cpu会收到一个来自硬盘的中断信号,并进入中断处理例程,手头正在执行的线程因此被打断,回到ready队列。而先前因I/O而waiting的线程随着I/O的完成也再次回到就绪队列,这时cpu可能会选择它来执行。
c.如何确定程序线程数?
  个人理解:如果所有的任务都是计算密集型的,则创建的多线程数=处理器核心数。如果io操作比较耗时,则根据具体情况调整线程数,此时 多线程数= n*处理器核心数。一般情况程序线程数等于cpu线程数的两到三倍就能很好的利用cpu了,过多的程序线程数不但不会提高性能,反而还会因为线程间的频繁切换而受影响,具体需要根据线程处理的业务考略,不断调整线程数个数,确定当前系统最优的线程数。
五种进程调度算法的总结:
  1、时间片轮转调度算法(RR):给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。
  2、先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。
  3、优先级调度算法(HPF):在进程等待队列中选择优先级最高的来执行。
  4、多级反馈队列调度算法:将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。优点是兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。
  5、高响应比优先调度算法:基于优先权的调度算法(FPPS),根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。
两种线程调度算法:
  线程调度是指系统为分配CPU处理器使用权,主要有两种:协同式调度和抢占式调度。
  1.协同式调度:使用协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,会主动通知系统切换到另一个线程上。好处:实现简单,且由于线程要把自己的事情干完后才会进行线程切换,切换对自己可见,所以没有线程同步的问题。坏处也很明显:线程执行时间不可控,当某个线程出现编写问题时会出现线程阻塞。
  2.抢占式调度:使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Tread.yeild()可以用于让出CPU执行时间,但是没有办法是可以获取CPU执行时间的)。好处:执行时间系统可控,不会出现一个线程导致整个进程阻塞的问题。
  Java中用到的线程调度算法:抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
  进程调度和线程调度的区别:
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程并不是一个可执行的实体,线程才是。

接下来请看:Java 并发编程学习笔记
https://blog.csdn.net/qq_41822345/article/details/104620428

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进击的程序猿~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值