强烈推荐的30道Java多线程面试题,赶紧收藏吧! 前言 本题集列举了众多IT公司面试真题,对应聘Java程序员职位的常见考点和知识体系都进行的分类和归纳整理。 本题集适合应聘Java和JavaEE职位的程序员作为面试复习、学习和强化的资料,也适合其他程序员作为拓展读物进行阅读。 本题集包含了常见的算法、面试题,也包含了新的高级技术,比如:微服务架构等技术的面试题目。本题集非常全面,对于工作1-5年左右的java程序员面试有非常好的指导作用。 大家也可以访问(直接在线观看最新版的面试题):
大学生高端复合人才成长
四个专业都要学,从零开始2000小时,成为高端人才,打下一生技术基础,不再是低端码农。
多线程: 1.下面程序的运行结果()(选择一项)
2.下列哪个方法可用于创建一个可运行的类()
3.说明类java.lang.ThreadLocal的作用和原理。列举在哪些程序中见过ThreadLocal的使用? 作用: 要编写一个多线程安全(Thread-safe)的程序是困难的,为了让线程共享资源,必须小心地对共享资源进行同步,同步带来一定的效能延迟,而另一方面,在处理同步的时候,又要注意对象的锁定与释放,避免产生死结,种种因素都使得编写多线程程序变得困难。 尝试从另一个角度来思考多线程共享资源的问题,既然共享资源这么困难,那么就干脆不要共享,何不为每个线程创造一个资源的复本。将每一个线程存取数据的行为加以隔离,实现的方法就是给予每个线程一个特定空间来保管该线程所独享的资源。 比如:在Hibernate中的Session就有使用。 ThreadLocal的原理 ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。 4.说说乐观锁与悲观锁 答:悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。 两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。 5.在Java中怎么实现多线程?描述线程状态的变化过程。 答:当多个线程访问同一个数据时,容易出现线程安全问题,需要某种方式来确保资源在某一时刻只被一个线程使用。需要让线程同步,保证数据安全线程同步的实现方案: 同步代码块和同步方法,均需要使用synchronized关键字 同步代码块:public void makeWithdrawal(int amt) { synchronized (acct) { } } 同步方法:public synchronized void makeWithdrawal(int amt) { } 线程同步的好处:解决了线程安全问题 线程同步的缺点:性能下降,可能会带来死锁 6.请写出多线程代码使用Thread或者Runnable,并说出两种的区别。 方式1:继承Java.lang.Thread类,并覆盖run() 方法。优势:编写简单;劣势:无法继承其它父类
方式2:实现Java.lang.Runnable接口,并实现run()方法。优势:可继承其它类,多线程可共享同一个Thread对象;劣势:编程方式稍微复杂,如需访问当前线程,需调用Thread.currentThread()方法
7.在多线程编程里,wait方法的调用方式是怎样的? 答:wait方法是线程通信的方法之一,必须用在 synchronized方法或者synchronized代码块中,否则会抛出异常,这就涉及到一个“锁”的概念,而wait方法必须使用上锁的对象来调用,从而持有该对象的锁进入线程等待状态,直到使用该上锁的对象调用notify或者notifyAll方法来唤醒之前进入等待的线程,以释放持有的锁。 8.Java线程的几种状态 答:线程是一个动态执行的过程,它有一个从产生到死亡的过程,共五种状态: 新建(new Thread) 当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动) 例如:Thread t1=new Thread(); 就绪(runnable) 线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start(); 运行(running) 线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。 死亡(dead) 当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。 自然终止:正常运行run()方法后终止 异常终止:调用stop()方法让一个线程终止运行 堵塞(blocked) 由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。 正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。 正在等待:调用wait()方法。(调用motify()方法回到就绪状态) 被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复) 9.在Java多线程中,请用下面哪种方式不会使线程进入阻塞状态()
10.volatile关键字是否能保证线程安全? 答:不能。虽然volatile提供了同步的机制,但是知识一种弱的同步机制,如需要强线程安全,还需要使用synchronized。 Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。 一、volatile的内存语义是: 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存中。 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。 二、volatile底层的实现机制 如果把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀指令。 1 、重排序时不能把后面的指令重排序到内存屏障之前的位置 2、使得本CPU的Cache写入内存 3、写入动作也会引起别的CPU或者别的内核无效化其Cache,相当于让新写入的值对别的线程可见。 11.请写出常用的Java多线程启动方式,Executors线程池有几种常用类型? (1) 继承Thread类
(2) 实现Runnable接口
在Executor框架下,利用Executors的静态方法可以创建三种类型的常用线程池: 1)FixedThreadPool这个线程池可以创建固定线程数的线程池。 2)SingleThreadExecutor是使用单个worker线程的Executor。 3)CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。 12.关于sleep()和wait(),以下描述错误的一项是()
13.进程和线程的区别是什么? 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
14.以下锁机机制中,不能保证线程安全的是()
15.创建n多个线程,如何保证这些线程同时启动?看清,是“同时”。 答:用一个for循环创建线程对象,同时调用wait()方法,让所有线程等待;直到最后一个线程也准备就绪后,调用notifyAll(), 同时启动所有线程。 比如:给你n个赛车,让他们都在起跑线上就绪后,同时出发,Java多线程如何写代码? 思路是,来一辆赛车就加上一把锁,并修改对应的操作数,如果没有全部就绪就等待,并释放锁,直到最后一辆赛车到场后唤醒所有的赛车线程。代码参考如下:
16.同步和异步有何异同,在什么情况下分别使用它们? 答:1.如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 2.当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。 3.举个例子: 打电话是同步 发消息是异步 17.Java线程中,sleep()和wait()区别 答:sleep是线程类(Thread)的方法;作用是导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复;调用sleep()不会释放对象锁。 wait是Object类的方法;对此对象调用wait方法导致本线程放弃对象锁,进入等 待此对象的等待锁定池。只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池,准备获得对象锁进行运行状态。 18.下面所述步骤中,是创建进程做必须的步骤是()
19.无锁化编程有哪些常见方法?()
20.sleep()和yield()有什么区别? 答:① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会; ② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态; ③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常; ④ sleep()方法比yield()方法(跟操作系统相关)具有更好的可移植性。
21.当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法? 答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。 只有等待当前线程执行完毕释放锁资源之后,其他线程才有可能进行执行该同步方法! 延伸 对象锁分为三种:共享资源、this、当前类的字节码文件对象 22.请说出与线程同步相关的方法。 答:1. wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; 2. sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常; 3. notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; 4. notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争; 5. JDK 1.5通过Lock接口提供了显式(explicit)的锁机制,增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象; 6. JDK 1.5还提供了信号量(semaphore)机制,信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。 下面的例子演示了100个线程同时向一个银行账户中存入1钱,在没有使用同步机制和使用同步机制情况下的执行情况。 银行账户类:
存钱线程类:
测试类:
在没有同步的情况下,执行结果通常是显示账户余额在10以下,出现这种状况的原因是,当一个线程A试图存入1的时候,另外一个线程B也能够进入存款的方法中,线程B读取到的账户余额仍然是线程A存入1钱之前的账户余额,因此也是在原来的余额0上面做了加1的操作,同理线程C也会做类似的事情,所以最后100个线程执行结束时,本来期望账户余额为100,但实际得到的通常在10以下。解决这个问题的办法就是同步,当一个线程对银行账户存钱时,需要将此账户锁定,待其操作完成后才允许其他的线程进行操作,代码有如下几种调整方案: 1. 在银行账户的存款(deposit)方法上同步(synchronized)关键字
2. 在线程调用存款方法时对银行账户进行同步
3. 通过JDK 1.5显示的锁机制,为每个银行账户创建一个锁对象,在存款操作进行加锁和解锁的操作
按照上述三种方式对代码进行修改后,重写执行测试代码Test01,将看到最终的账户余额为100。 23.编写多线程程序有几种实现方式? 答:Java 5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,同时也可以实现资源共享,显然使用Runnable接口更为灵活。 补充:Java 5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,代码如下所示:
24.synchronized关键字的用法? 答:synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。在第60题的例子中已经展示了synchronized关键字的用法。 25.启动一个线程是用run()还是start()方法? 答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。 API解释如下:
26.什么是线程池(thread pool)? 答:在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示: newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。 有通过Executors工具类创建线程池并使用线程池执行线程的代码。如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。 27.线程的基本状态以及状态之间的关系?
除去起始(new)状态和结束(finished)状态,线程有三种状态,分别是:就绪(ready)、运行(running)和阻塞(blocked)。其中就绪状态代表线程具备了运行的所有条件,只等待CPU调度(万事俱备,只欠东风);处于运行状态的线程可能因为CPU调度(时间片用完了)的原因回到就绪状态,也有可能因为调用了线程的yield方法回到就绪状态,此时线程不会释放它占有的资源的锁,坐等CPU以继续执行;运行状态的线程可能因为I/O中断、线程休眠、调用了对象的wait方法而进入阻塞状态(有的地方也称之为等待状态);而进入阻塞状态的线程会因为休眠结束、调用了对象的notify方法或notifyAll方法或其他线程执行结束而进入就绪状态。注意:调用wait方法会让线程进入等待池中等待被唤醒,notify方法或notifyAll方法会让等待锁中的线程从等待池进入等锁池,在没有得到对象的锁之前,线程仍然无法获得CPU的调度和执行。 28.简述synchronized 和java.util.concurrent.locks.Lock的异同? 答:Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock 有比synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而Lock 一定要求程序员手工释放,并且必须在finally 块中释放(这是释放外部资源的最好的地方)。 29.创建线程的两种方式分别是什么,优缺点是什么? 方式1:继承Java.lang.Thread类,并覆盖run() 方法。 优势:编写简单; 劣势:单继承的限制—-无法继承其它父类,同时不能实现资源共享。
方式2:实现Java.lang.Runnable接口,并实现run()方法。 优势:可继承其它类,多线程可共享同一个Thread对象; 劣势:编程方式稍微复杂,如需访问当前线程,需调用Thread.currentThread()方法
30.Java创建线程后,调用start()方法和run()的区别 两种方法的区别 1) start方法: 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。 2) run(): run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待,run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。 总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。 两种方式的比较 : 实际中往往采用实现Runable接口,一方面因为java只支持单继承,继承了Thread类就无法再继续继承其它类,而且Runable接口只有一个run方法;另一方面通过结果可以看出实现Runable接口才是真正的多线程。
31.线程的生命周期 线程是一个动态执行的过程,它也有一个从产生到死亡的过程。 生命周期的五种状态 新建(new Thread) 当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动) 例如:Thread t1=new Thread(); 就绪(runnable) 线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start(); 运行(running) 线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。 死亡(dead) 当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。 自然终止:正常运行run()方法后终止 异常终止:调用stop()方法让一个线程终止运行 堵塞(blocked) 由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。 正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。 正在等待:调用wait()方法。(调用motify()方法回到就绪状态) 被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复) 32.如何实现线程同步? 当多个线程访问同一个数据时,容易出现线程安全问题,需要某种方式来确保资源在某一时刻只被一个线程使用。需要让线程同步,保证数据安全 线程同步的实现方案: 1)同步代码块,使用synchronized关键字 同步代码块: synchronized (同步锁) { 授课代码; } 同步方法: public synchronized void makeWithdrawal(int amt) { } 线程同步的好处:解决了线程安全问题 线程同步的缺点:性能下降,可能会带来死锁 注意: 同步代码块,所使用的同步锁可以是三种, 1、this 2、 共享资源 3、 字节码文件对象 同步方法所使用的同步锁,默认的是this 33.说说关于同步锁的更多细节 答:Java中每个对象都有一个内置锁。 当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为锁、锁定对象、在对象上锁定或在对象上同步。 当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。 释放锁是指持锁线程退出了synchronized同步方法或代码块。 关于锁和同步,有一下几个要点: 1)只能同步方法,而不能同步变量和类; 2)每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步? 3)不必同步类中所有的方法,类可以同时拥有同步和非同步方法。 4)如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。 5)如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。 6)线程睡眠时,它所持的任何锁都不会释放。 7)线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则了两个对象的同步锁。 8)同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。 9)在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要哪个对象的锁。 34.Java中实现线程通信的三个方法的作用是什么? Java提供了3个方法解决线程之间的通信问题,均是java.lang.Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。
java面试题合集内容过多,在此只更新一部分,需要完整版java面试题集的朋友们,评论区留言,我看到发给你!
文章内容整理不易,点赞收藏走一波呗~
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/22923.html