Java多线程面试题(面试官常问)

进程和线程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是从一个进程从创建、运行到消亡的过程。在Java中,当我们启动main函数时其实就是启动了一个JVM的进程,而mian函数所在的线程就是这个进程中的一个线程,称为主线程。

线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程都有自己的程序计数器、虚拟机和本地方法栈,所以系统在产生一个线程,或在各个线程之间切换工作是,负担要比进程小很多,所以线程也称轻量级进程。

并发和并行

  • 并发:同一时间段内,多个任务都在执行(单位时间内不一定同时执行)
  • 并行:单位时间内,多个任务同时执行。

上下文切换

多线程编程中一般线程的个数都大于CPU核心的个数,而一个CPU核心在任意时刻内只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略时为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程属于一次上下文切换。

换句话说,当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。

sleep()和wait()

  • 最主要的区别是sleep()方法没有释放锁,而wait()方法释放了锁。
  • 两者都可以暂停线程的执行。
  • wait()通常用于线程间交互/通信,sleep()通常用于暂停执行。
  • wait()方法被调用后,线程不会自动苏醒(除非超时),需要别的线程调用同一个对象上的notify()notifyAll()方法。而sleep()方法执行完后,线程会自动苏醒。

start()和run()

为什么调用start()方法时会执行run()方法,为什么不能直接调用run()方法?

当我们new一个Thread时,线程进入了新建状态,调用start()方法,会启动一个线程并使线程进入就绪状态,等分到时间片后就可以开始运行了。
start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。
而直接执行run()方法会把run方法当作一个main线程下的普通方法去执行,并不是在某个线程中执行它,所以这不是多线程工作。

synchronized关键字

synchronized关键字是解决多个线程之间访问资源的同步性,可以保证被它修饰的方法或代码块在任意时刻只能有一个线程执行。

synchronized主要的三种使用方式:

1.修饰实例方法

作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。

2.修饰静态方法

给当前类加锁,会作用于类的所有对象实例,因为静态成员是类成员,不属于任何一个实例对象,所以线程A调用一个实例对象的非静态synchronized方法,而线程B调用该实例对象所属类的静态synchronized方法时是允许的,不会冲突互斥。因为访问静态synchronized方法占用的是当前类的锁,而访问非静态synchronized方法占用的是当前实例对象锁。

3.修饰代码块

指定加锁对象,进入同步代码库前要获得给定对象的锁。

synchronized和ReentrantLock:

1.两者都是可重入锁
即自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁时还是可以获取的。

2.前者依赖JVM而后者依赖API
synchronized是依赖于JVM实现的,ReentrantLock是依赖于JDK层面实现的。

3.ReentrantLock比synchronized功能多
ReentrantLock增加了一些高级功能,主要说有三点:①等待可中断②可实现公平锁③可实现选择性通知。

volatile关键字

当前Java内存模型下,线程可以把变量保存到本地内存(如寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还在继续使用它在寄存器中变量值的拷贝,造成数据的不一致。

volatile关键字就是解决这个问题,指示JVM这个变量不稳定,每次使用它都要到主存中进行读取。除此之外还有一个重要的作用是防重排。

并发执行的三个重要特性:

1.原子性

要么所有的操作都得到执行并且不会收到任何因素干扰而中断,要么所有的操作都不执行。可使用synchronized来保证代码原子性。

2.可见性
当对一个共享变量进行了修改后,那么另外的线程都是立即可以看到修改后的最新值。volatile可以保证可见性。

3.有序性
代码在执行过程中的先后顺序,Java在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序,即指令重排。volatile可以禁止指令重排优化。

ThreadLocal

通常情况下,我们创建的变量时可以被任何一个线程访问并修改的。如果要实现每一个线程都有自己的专属本地变量该如何解决?这就需要ThreadLocal类了。

ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

当创建一个ThreadLocal变量后,访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal名称的由来。可以使用get()和set()方法来获取默认值或将其值更改为当前线程所存副本的值,从而避免了线程安全问题。

实际上,ThreadLocal类有一个静态内部类ThreadLocalMap,可以把ThreadLocalMap看作是ThreadLocal类定制的HashMap,最终的变量是放在了当前线程的ThreadLocalMap中,而不是ThreadLocal类上,可以看作ThreadLocal类是ThreadLocalMap的封装,传递了值。

如果再同一个线程中声明了两个ThradLocal对象的话,会使用Thread内部仅有的那个ThreadLocalMap存放数据的,TheadLocalMap的key就是ThreadLocal对象,value就是ThreadLocal对象调用set方法设置的值。

ThreadLocalMap使用的key为ThreadLocal的弱引用,而value是强引用。所以ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而value不会被
清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时会清理掉key为null 的记录。使用完ThreadLocal方法后最好手动调用remove()方法。

(插播反爬信息 )博主CSDN地址:https://wzlodq.blog.csdn.net/

线程池

池化技术大家应该很熟悉,线程池、数据库连接池、Http连接池等等都是对这思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
使用线程池,可以降低资源消耗、提高响应速度、提高线程的可管理性。

Runnable和Callable

Runnable接口不会返回结果或抛出检查异常,但Callable接口可以。
工具类Excutors可以实现Runnable对对象和Callable对象之间的相互转换。

@FunctionalInterface
public interface Runnable{
 //没有返回值也无法抛出异常
 public abstract void run();
}
@FunctionalInterface
public interface Callable<V>{
 //@return 计算得出结果
 //@throws 如果无法计算结果,则抛出异常
}

execute()和submit()

  1. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否。
  2. submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型对象,通过这个对象可以判断任务是否执行成功。

创建线程池

  1. 通过构造器ThreadpoolExecutor实现(下面介绍)。
  2. 通过工具类Executors实现(不推荐)

ThreadPoolExecutor: .

  • FixedThreadPool:该访法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
  • SingleThreadExecutor:方法返回- 个只有一一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool:该方法返回一个可 根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。

ThreadPoolExecutor

  • ThreadPoolExecutor构造函数重要参数分析:
  • corePoolSize:核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。
  • maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数量变为最⼤线程数。
  • workQueue:当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达到的话,新任务就会被存放在队列中。
  • keepAliveTime:当线程池中的线程数量⼤于 corePoolSize 的时候,如果这时没有新的任务提交,核⼼线程外的线程不会⽴即销毁,⽽是会等待,直到等待的时间超过了keepAliveTime 才会被回收销毁;
  • unit:keepAliveTime 参数的时间单位。
  • threadFactory:executor 创建新线程的时候会⽤到。
  • handler:饱和策略

①ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException 来拒绝新任务的处理。
②ThreadPoolExecutor.CallerRunsPolicy:调⽤执⾏⾃⼰的线程运⾏任务。会降低对于新任务提交速度,影响程序的整体性能,另外会增加队列容量。
③ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
④ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

Demo

模拟了 10 个任务,我们配置的核⼼线程数为 5 、等待队列容量为 100 ,所以每次只能存在5个任务同时执⾏,剩下的5个任务会被放到等待队列中去。当前的 5 个任务之⾏完成后,才会之⾏剩下的 5 个任务。

public class MyRunnable implements Runnable {
 private String command;
 public MyRunnable(String s) {
  this.command = s;
 }
 @Override
 public void run() {
  System.out.println(Thread.currentThread().getName() + "开始时间:" + new Date());
    processCommand();
  System.out.println(Thread.currentThread().getName() + "结束时间:" + new Date());
 }
 private void processCommand() {
  try {
   Thread.sleep(3000); //设花费3秒执行任务
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
 @Override
 public String toString() {
  return this.command;
 }
}
public class Demo {
 private static final int CORE_POOL_SIZE = 5;//核⼼线程数为 5
 private static final int MAX_POOL_SIZE = 10;//最⼤线程数 10
 private static final int QUEUE_CAPACITY = 100;//容量100
 private static final Long KEEP_ALIVE_TIME = 1L;//等待时间为 1L
 public static void main(String[] args) {
  //通过ThreadPoolExecutor构造函数⾃定义参数创建
  ThreadPoolExecutor executor = new ThreadPoolExecutor(
    CORE_POOL_SIZE,
    MAX_POOL_SIZE,
    KEEP_ALIVE_TIME,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(QUEUE_CAPACITY),
    new ThreadPoolExecutor.CallerRunsPolicy());//饱和策略
  for (int i = 0; i < 10; i++) {
   //创建WorkerThread对象(WorkerThread类实现了Runnable接⼝)
   Runnable worker = new MyRunnable("" + i);
   executor.execute(worker);//执⾏Runnable
  }
  executor.shutdown();//终⽌线程池
  while (!executor.isTerminated()) {
  }
  System.out.println("结束");
 }
}
/*运行结果如下:
pool-1-thread-3开始时间:Mon Mar 29 22:46:02 CST 2021
pool-1-thread-2开始时间:Mon Mar 29 22:46:02 CST 2021
pool-1-thread-4开始时间:Mon Mar 29 22:46:02 CST 2021
pool-1-thread-5开始时间:Mon Mar 29 22:46:02 CST 2021
pool-1-thread-1开始时间:Mon Mar 29 22:46:02 CST 2021
pool-1-thread-2结束时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-2开始时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-3结束时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-3开始时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-4结束时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-4开始时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-5结束时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-5开始时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-1结束时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-1开始时间:Mon Mar 29 22:46:07 CST 2021
pool-1-thread-2结束时间:Mon Mar 29 22:46:12 CST 2021
pool-1-thread-3结束时间:Mon Mar 29 22:46:12 CST 2021
pool-1-thread-4结束时间:Mon Mar 29 22:46:12 CST 2021
pool-1-thread-5结束时间:Mon Mar 29 22:46:12 CST 2021
pool-1-thread-1结束时间:Mon Mar 29 22:46:12 CST 2021
结束
*/

到此这篇关于Java多线程面试题(面试官常问)的文章就介绍到这了,更多相关Java多线程面试题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java多线程面试题(面试官常问)

    进程和线程 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的.系统运行一个程序即是从一个进程从创建.运行到消亡的过程.在Java中,当我们启动main函数时其实就是启动了一个JVM的进程,而mian函数所在的线程就是这个进程中的一个线程,称为主线程. 线程是比进程更小的执行单位.一个进程在其执行的过程中可以产生多个线程.与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程都有自己的程序计数器.虚拟机和本地方法栈,所以系统在产生一个线程,或在各个线程之间切换工作是,

  • 15个高级Java多线程面试题及回答

    Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题.在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的.他们会问面试者很多令人混淆的Java线程问题.面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面.用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的.下面这些是我在不同时间不同地点喜欢问的Jav

  • 15个顶级Java多线程面试题(附答案)

    在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分.如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题.在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的.他们会问面试者很多令人混淆的Java线程问题.面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面.用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的.下面这些是我在不同时间不同地点喜欢问的Java线程问题.我没有提供答

  • Java多线程和并发基础面试题(问答形式)

    本文帮助大家掌握Java多线程基础知识来对应日后碰到的问题,具体内容如下 一.Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单一进程.线程可以被称为轻量级进程.线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源. 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,C

  • Java常见面试题之多线程和高并发详解

    volatile 对 volatile的理解 volatile 是一种轻量级的同步机制. 保证数据可见性 不保证原子性 禁止指令重排序 JMM JMM(Java 内存模型)是一种抽象的概念,描述了一组规则或规范,定义了程序中各个变量的访问方式. JVM运行程序的实体是线程,每个线程创建时 JVM 都会为其创建一个工作内存,是线程的私有数据区域.JMM中规定所有变量都存储在主内存,主内存是共享内存.线程对变量的操作在工作内存中进行,首先将变量从主内存拷贝到工作内存,操作完成后写会主内存.不同线程间

  • 2018版java多线程面试题集合及答案

    java多线程面试题整理及答案,供大家参考,具体内容如下 1.什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速.比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒.Java在语言层面对多线程提供了卓越的支 持,它也是一个很好的卖点. 2.线程和进程有什么区别? 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务.不同的进程使用

  • SpringBoot服务监控机制原理解析(面试官常问)

    前言 任何一个服务如果没有监控,那就是两眼一抹黑,无法知道当前服务的运行情况,也就无法对可能出现的异常状况进行很好的处理,所以对任意一个服务来说,监控都是必不可少的. 就目前而言,大部分微服务应用都是基于 SpringBoot 来构建,所以了解 SpringBoot 的监控特性是非常有必要的,而 SpringBoot 也提供了一些特性来帮助我们监控应用. 本文基于 SpringBoot 2.3.1.RELEASE 版本演示. SpringBoot 监控 SpringBoot 中的监控可以分为 H

  • 面试官常问React的生命周期问题

    React的生命周期 两张图带你理解 React的生命周期 React的生命周期(旧) class Life extends React.Component{ // 构造器 constructor(props){ console.log('Life构造器---constructor'); super(props) this.state={num:0} } // 计算+1功能 add=()=>{ const {num} = this.state this.setState({num:num+1})

  • 面试官常问之说说js中var、let、const的区别

    前言 关于 var.let 和 const 三个关键字的区别,是一个老生常谈的问题,也是经典的面试题.本篇文章将全面讲解三者的特性,以及它们之间的区别,由浅入深让你彻底搞懂这个知识点. 变量声明 ECMAScript 变量是松散类型的,意思就是变量可以用于保存任何类型的数据,每个变量只不过是一个用于保存任意值的命名占位符. 有3个关键字可以声明变量:var.let和const,var在 ECMAScript 的所有版本中都可以使用,而let和const只能在 ES6 及更晚的版本中使用. var

  • 面试最常问的13种Vue修饰符

    目录 1.lazy 2.trim 3.number 4.stop 5.capture 6.self 7.once 8.prevent 9.native 10.left,right,middle 11.passive 12.camel 12.sync 13.keyCode 1.lazy lazy修饰符作用是,改变输入框的值时value不会改变,当光标离开输入框时,v-model绑定的值value才会改变 <input type="text" v-model.lazy="v

  • 高价值Java多线程面试题分析

    问题一 A线程正在执行一个对象中的同步方法,B线程是否可以同时执行同一个对象中的非同步方法? 可以,两个线程运行所需资源不同,不需要抢占. 案例一. package duoxiancheng2; /** * @author yeqv * @program A2 * @Classname Ms1 * @Date 2022/2/7 19:08 * @Email w16638771062@163.com */ public class Ms1 { //A线程正在执行一个对象中的同步方法,B线程是否可以

  • 当面试官问我ArrayList和LinkedList哪个更占空间时,我是这么答的(面试官必问)

    前言 今天介绍一下Java的两个集合类,ArrayList和LinkedList,这两个集合的知识点几乎可以说面试必问的. 对于这两个集合类,相信大家都不陌生,ArrayList可以说是日常开发中用的最多的工具类了,也是面试中几乎必问的,LinkedList可能用的少点,但大多数的面试也会有所涉及,尤其是关于这两者的比较可以说是家常便饭,所以,无论从使用上还是在面试的准备上,对于这两个类的知识点我们都要有足够的了解. ArrayList ArrayList是List接口的一个实现类,底层是基于数

随机推荐