android多线程面试题参考_第1页
android多线程面试题参考_第2页
android多线程面试题参考_第3页
android多线程面试题参考_第4页
android多线程面试题参考_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1.什么是线程

线程就是进程中运行的多个子任务,是操作系统调用的最小单元

2.线程的状态

New:新建状态,new出来,还没有调用start

Runnable:可运行状态,调用start进入可运行状态,可能运行也可能没有运行,取

决于操作系统的调度

Blocked:阻塞状态,被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入

synchronized关键字修饰的方法或代码块(获取锁)时的状态。

Waiting:等待状态,不活动,不运行任何代码,等待线程调度器调度,wait

sleep

TimedWaiting:超时等待,在指定时间自行返回

Terminated:终止状态,包括正常终止和异常终止

3.线程的创建

a.继承Thread重写run方法

b.实现Runnable重写run方法

c.实现Callable重写call方法

实现Callable和实现Runnable类似,但是功能更强大,具体表现在

a.可以在任务结束后提供一个返回值,Runnable不行

b.call方法可以抛出异常,Runnable的run方法不行

c.可以通过运行Callable得到的Fulture对象监听目标线程调用call方法的结果,得

到返回值,(fulture.get。,调用后会阻塞,直到获取到返回值)

4.线程中断

一般情况下,线程不执行完任务不会退出,但是在有些场景下,我们需要手动控

制线程中断结束任务,Java中有提供线程中断机制相关的Api,每个线程都一个状态

位用于标识当前线程对象是否是中断状态

publicbooleanislnterrupted。〃判断中断标识位是否是true,不会改变标识位

publicvoidinterruptO〃将中断标识位设置为truepublicstaticboolean

interrupted。〃判断当前线程是否被中断,并且该方法调用结束的时候会清空中断

标识位

需要注意的是interrupt()方法并不会真的中断线程,它只是将中断标识位设置

为true,具体是否要中断由程序来判断,如下,只要线程中断标识位为false,也就是

没有中断就一直执行线程方法

newThread(newRunnable(){

while(!Thread.currentThread().islnterrupted()){

〃执行线程方法

}

)).start();

前边我们提到了线程的六种状态,NewRunnableBlockedWaitingTimed

WaitingTerminated,那么在这六种状态下调用线程中断的代码会怎样呢,New

和Terminated状态下,线程不会理会线程中断的请求,既不会设置标记位,在

Runnable和Blocked状态下调用interrupt会将标志位设置位true,在Waiting和

TimedWaiting状态下会发生InterruptedException异常,针对这个异常我们如何

处理?

1.在catch语句中通过interrupt设置中断状态,因为发生中断异常时,中断标志位

会被复位,我们需要重新将中断标志位设置为true,这样外界可以通过这个状态判

断是否需要中断线程

try(

}catch(lnterruptedExceptione){

Thread.currentThread().interrupt();

)

2.更好的做法是,不捕获异常,直接抛出给调用者处理,这样更灵活

5.Thread为什么不能用stop方法停止线程

从SUN的官方文档可以得知,调用Thread.stopO方法是不安全的,这是因为当调

用Thread.stopO方法时,会发生下面两件事:

1.即刻抛出ThreadDeath异常,在线程的run。方法内,任何一点都有可能抛出

ThreadDeathError,包括在catch或finally语句中。

2.释放该线程所持有的所有的锁。调用thread.stopO后导致了该线程所持有的所

有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些

被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

6.volatile关键字

volatile为实例域的同步访问提供了免锁机制,如果声明一个域为volatile,那么编

译器和虚拟机就直到该域可能被另一个线程并发更新

7.java内存模型

堆内存是被所有线程共享的运行时内存区域,存在可见性的问题。线程之间共享

变量存储在主存中,每个线程都有一个私有的本地内存,本地内存存储了该线程

共享变量的副本(本地内存是一个抽象概念,并不真实存在),两个线程要通信

的话,首先A线程把本地内存更新过的共享变量更新到主存中,然后B线程去主存

中读取A线程更新过的共享变量,也就是说假设线程A执行了i=1这行代码更新主

线程变量i的值,会首先在自己的工作线程中堆变量i进行赋值,然后再写入主存当

中,而不是直接写入主存

8.原子性可见性有序性

原子性:对基本数据类型的读取和赋值操作是原子性操作,这些操作不可被中

断,是一步到位的,例如x=3是原子性操作,而y=x就不是,它包含两步:第一

读取x,第二将x写入工作内存;x++也不是原子性操作,它包含三部,第一,读取

x,第二,对x加1,第三,写入内存。原子性操作的类如:Atomiclnteger

AtomicBooleanAtomicLongAtomicReference

可见性:指线程之间的可见性,既一个线程修改的状态对另一个线程是可见的。

volatile修饰可以保证可见性,它会保证修改的值会立即被更新到主存,所以对其

他线程是可见的,普通的共享变量不能保证可见性,因为被修改后不会立即写入

主存,何时被写入主存是不确定的,所以其他线程去读取的时候可能读到的还是

旧值

有序性:Java中的指令重排序(包括编译器重排序和运行期重排序)可以起到优

化代码的作用,但是在多线程中会影响到并发执行的正确性,使用volatile可以保

证有序性,禁止指令重排

voEile可以保证可见性有序性,但是无法保证原子性,在某些情况下可以提供优

于锁的性能和伸缩性,替代sychronized关键字简化代码,但是要严格遵循使用条

件。

9.线程池ThreadPoolExecutor

线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源

的消耗,当一个任务提交到线程池时

a.首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程

执行任务,否则进入下一步

b.判断工作队列是否已满,没有满则加入工作队列,否则执行下一步

c.判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则

执行饱和策略,默认抛出异常

10.线程池的种类

I.FixedThreadPool:可重用固定线程数的线程池,只有核心线程,没有非核心线

程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没

有则加入队列排队

2.SingleThreadExecutor:单线程线程池,只有一个核心线程,没有非核心线程,

当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入

队列等待,可以保证所有任务在一个线程中按照顺序执行,和FixedThreadPool的

区别只有数量

SCachedThreadPool:按需创建的线程池,没有核心线程,非核心线程有

lnteger.MAX_VALUE个,每次提交

任务如果有空闲线程则由空闲线程执行,没有空闲线程则创建新的线程执行,适

用于大量的需要立即处理的并且耗时较短的任务

4.ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor,用于延时执行任

务或定期执行任务,核心线程数固定,线程总数为lnteger.MAX_VALUE

11.线程同步机制与原理,举例说明

为什么需要线程同步?当多个线程操作同一个变量的时候,存在这个变量何时对

另一个线程可见的问题,也就是可见性。每一个线程都持有主存中变量的一个副

本,当他更新这个变量时,首先更新的是自己线程中副本的变量值,然后会将这

个值更新到主存中,但是是否立即更新以及更新到主存的时机是不确定的,这就

导致当另一个线程操作这个变量的时候,他从主存中读取的这个变量还是旧的

值,导致两个线程不同步的问题。线程同步就是为了保证多线程操作的可见性和

原子性,比如我们用synchronized关键字包裹一端代码,我们希望这段代码执行

完成后,对另一个线程立即可见,另一个线程再次操作的时候得到的是上一个线

程更新之后的内容,还有就是保证这段代码的原子性,这段代码可能涉及到了好

几部操作,我们希望这好几步的操作一次完成不会被中间打断,锁的同步机制就

可以实现这一点。一般说的synchronized用来做多线程同步功能,其实

synchronized只是提供多线程互斥,而对象的wait。和notify。方法才提供线程的

同步功能。JVM通过Monitor对象实现线程同步,当多个线程同时请求

synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线

程。新请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有锁的

线程unlock之后,则排队队列里的线程竞争上岗(synchronized是不公平竞争

锁,下面还会讲到)。如果运行的线程调用对象的wait。后就释放锁并进入wait线

程集合那边,当调用对象的notify。或notifyallO后,wait线程就到排队那边。这是

大致的逻辑。

12.arrayList与linkedList的读写时间复杂度

(1)ArrayList:ArrayList是一个泛型类,底层采用数组结构保存对象。数组结构

的优点是便于对集合进行快速的随机访问,即如果需要经常根据索引位置访问集

合中的对象,使用由ArrayList类实现的List集合的效率较好。数组结构的缺点是向

指定索引位置插入对象和删除指定索引位置对象的速度较慢,并且插入或删除对

象的索引位置越小效率越低,原因是当向指定的索引位置插入对象时,会同时将

指定索引位置及之后的所有对象相应的向后移动一位。

(2)LinkedList:LinkedList是一个泛型类,底层是一个双向链表,所以它在执

行插入和删除操作时比ArrayList更加的高效,但也因为链表的数据结构,所以在

随机访问方面要比ArrayList差。

ArrayList是线性表(数组)

get()直接读取第几个下标,复杂度0(1)

add(E)添加元素,直接在后面添加,复杂度0(1)

add(index,E)添加元素,在第几个元素后面插入,后面的元素需要向后移动,复

杂度。(n)

remove()删除元素,后面的元素需要逐个移动,复杂度。(n)

LinkedList是链表的操作

get()获取第几个元素,依次遍历,复杂度0(n)

add(E)添加到末尾,复杂度。⑴

adcKindex,E)添加第几个元素后,需要先查找到第几个元素,直接指针指向操

作,复杂度0(n)

remove()删除元素,直接指针指向操作,复杂度0(1)

13.为什么HashMap线程不安全(hash碰撞与扩容导致)

HashMap的底层存储结构是一个Entry数组,每个Entry又是一个单链表,一旦发

生Hash冲突的的时候,HashMap采用拉链法解决碰撞冲突,因为hashMap的put

方法不是同步的,所以他的扩容方法也不是同步的,在扩容过程中,会新生成一

个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,

之后指向新生成的数组。当多个线程同时检测到hashmap需要扩容的时候就会同

时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,

结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢

失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值

的table作为原始数组,这样也会有问题。扩容的时候可能会引发链表形成环状结

14.进程线程的区别

1.地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址

空间。

2.资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进

程之间的资源是独立的。

3.一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整

个进程都死掉。所以多进程要比多线程健壮。

4.进程切换时,消耗的资源大,效率不高。所以涉及到频繁的切换时,使用线程要

好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线

程不能用进程

5.执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入

口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程

执行控制。

6.线程是处理器调度的基本单位,但是进程不是。

7.两者均可并发执行。

15.Binder的内存拷贝过程

相比其他的IPC通信,比如消息机制、共享内存、管道、信号量等,Binder仅需一

次内存拷贝,即可让目标进程读取到更新数据,同共享内存一样相当高效,其他

的IPC通信机制大多需要2次内存拷贝。Binder内存拷贝的原理为:进程A为Binder

客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder驱动的内核空间,由

于进程B在打开Binder设备(/dev/binder)时,已将Binder驱动的内核空间映射

(mmap)到自己的进程空间,所以进程B可以直接看到Binder驱动内核空间的内容

改动

16.传统IPC机制的通信原理(2次内存拷贝)

1.发送方进程通过系统调用(copy_from_user)将要发送的数据存拷贝到内核缓存

区中。

2才妾收方开辟一段内存空间,内核通过系统调用(copy_to_user)将内核缓存区中

的数据拷贝到接收方的内存缓存区。

种传统IPC机制存在2个问题:

1.需要进行2次数据拷贝,第1次是从发送方用户空间拷贝到内核缓存区,第2次是

从内核缓存区拷贝到接收方用户空间。

2才妾收方进程不知道事先要分配多大的空间来接收数据,可能存在空间上的浪费。

17.Java内存模型(记住堆栈是内存分区,不是模型)

Java内存模型(即JavaMemoryModel,简称JMM)本身是一种抽象的概念,并不

真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量

(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行

程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方

称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储

在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作

(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内

存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作

主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作

内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,

线程间的通信(传值)必须通过主内存来完成

18.类的加载过程

类加载过程主要包含加载、验证、准备、解析、初始化、使用、卸载七个方面,

下面一一阐述。

1.加载:获取定义此类的二进制字节流,生成这个类的java.Iang.Class对象

2.验证:保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害

3.准备:准备阶段为变量分配内存并设置类变量的初始化

4.解析:解析过程是将常量池内的符号引用替换成直接引用

5.初始化:不同于准备阶段,本次初始化,是根据程序员通过程序制定的计划去初

始化类的变量和其他资源。这些资源有static。块,构造函数,父类的初始化等

6.使用:使用过程就是根据程序定义的行为执行

7.卸载:卸载由GC完成。

19.什么情况下会触发类的初始化

1、遇到new,getstatic,putstatic,invokestatic这4条指令;

2、使用java.lang.reflect包的方法对类进行反射调用;

3、初始化一个类的时候,如果发现其父类没有进行过初始化,则先初始化其父类

(注意!如果其父类是接口的话,则不要求初始化父类);

4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个

类),虚拟机会先初始化这个主类;

5、当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实

例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,

并且这个方法句柄所对应的类没有进行过初始化,则先触发其类初始化;

20.双亲委托模式

类加载器查找class所采用的是双亲委托模式,所谓双亲委托模式就是判断该类是

否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依

次进行递归,直到委托到最顶层的BootstrapClassLoader,如果Bootstrap

ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,

如果还没找到则最后交给自身去查找

21.双亲委托模式的好处

1.避免重复加载,如果已经加载过一次Class,则不需要再次加载,而是直接读取

已经加载的Class

2.更加安全,确保,java核心api中定义类型不会被随意替换,比如,采用双亲委

托模式可以使得系统在Java虚拟机启动时旧加载了String类,也就无法用自定义的

String类来替换系统的String类,这样便可以防止核心API库被随意篡改。

22.死锁的产生条件,如何避免死锁

死锁的四个必要条件

1.互斥条件:一个资源每次只能被一个进程使用

2.请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而

该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不

放。

3.不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺

走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

4循环等待条件:若干进程间形成首尾相接循环等待资源的关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要

上述条件之一不满足,就不会发生死锁。

避免死锁的方法:系统对进程发出每一个系统能够满足的资源申请进行动态检查,

并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则

予以分配,这是一种保证系统不进入死锁状态的动态策略。

在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发

生死锁。一般来说互斥条件是无法破坏的,所以在预防死锁时主要从其他三个方

面入手:

⑴破坏请求和保持条件:在系统中不允许进程在已获得某种资源的情况下,申请

其他资源,即要想出一个办法,阻止进程在持有资源的同时申请其它资源。

方法一:在所有进程开始运行之前,必须一次性的申请其在整个运行过程中所需

的全部资源,

方法二:要求每个进程提出新的资源申请前,释放它所占有的资源

(2)破坏不可抢占条件:允许对资源实行抢夺。

方式一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必

须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。

方式二:如果一个进程请求当前被另一个进程占有的资源,则操作系统可以抢占

另一个进程,要求它释放资源,只有在任意两个进程的优先级都不相同的条件

下,该方法才能预防死锁。

(3)破坏循环等待条件

对系统所有资源进行线性排序并赋予不同的序号,这样我们便可以规定进程在申

请资源时必须按照序号递增的顺序进行资源的申请,当以后要申请时需检查要申

请的资源的编号大于当前编号时,才能进行申请。

利用银行家算法避免死锁:

所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死

锁。如果会死锁,则不分配,否则就分配。

按照银行家算法的思想,当进程请求资源时,系统将按如下原则分配系统资源:

24.App启动流程

App启动时,AMS会检查这个应用程序所需要的进程是否存在,不存在就会请求

Zygote进程启动需要的应用程序进程,Zygote进程接收到AMS请求并通过fock自

身创建应用程序进程,这样应用程序进程就会获取虚拟机的实例,还会创建

Binder线程池(ProcessState.startThreadPoolO)和消息循环(ActivityThread

looper.loop),然后App进程,通过BinderIPC向sytem_server进程发起

attachApplication请求;system_server进程在收到请求后,进行一系列准备工作

后,再通过Binde门PC向App进程发送scheduleLaunchActivity请求;App进程的

binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送

LAUNCH_ACTIVITY消息;主线程在收到Message后,通过反射机制创建目标

Activity,并回调Activity.onCreateO等方法。到此,App便正式启动,开始进入

Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可

以看到App的主界面。

25.Android单线程模型

Android单线程模型的核心原则就是:只能在UI线程(MainThread)中对UI进行处

理。当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main

Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接

触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。

所以主线程通常又被叫做UI线程。在开发Android应用时必须遵守单线程模型的原

则:AndroidUI操作并不是线程安全的并且这些操作必须在UI线程中执行。

Android的单线程模型有两条原则:

1.不要阻塞UI线程。

2.不要在UI线程之外访问AndroidUItoolkit(主要是这两个包中的组件:

android.widgetandandroid.view

26.RecyclerView在很多方面能取代ListView,Google为什么没把

Listview划上一条过时的横线?

ListView采用的是RecyclerBin的回收机制在一些轻量级的List显示时效率更高。

27.HashMap如何保证元素均匀分布

hash&(length-1)

通过Key值的hashCode值和hashMap长度-1做与运算

hashmap中的元素,默认情况下,数组大小为16,也就是2的4次方,如果要自定

义HashMap初始化数组长度,也要设置为2的n次方大小,因为这样效率最高。因

为当数组长度为2的n次嘉的时候,不同的key算出的index相同的几率较小,那么

数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就

不用遍历某个位置上的链表,这样查询效率也就较高了

28.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执

行,T3在T2执行完后执行?

这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对“join”方法

是否熟

悉。这个多线程问题比较简单,可以用join方法实现。

29.在Java中Lock接口比synchronized块的优势是什么?你需要实

现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来

保持它的完整性,你会怎样去实现它?

lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能

满足你写

像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试

的问题越来

越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读

一下

Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。

30.在java中wait和sleep方法的不同?

通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时

wait会释放

锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停

执行。

31用Java实现阻塞队列。

这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测

侯选者是

否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且

你可以根

据这个问很多问题。如果他用wait。和notify。方法来实现阻塞队列,你可以要求

他用最新

的Java5中的并发类来再写一次。

32.用Java写代码来解决生产者一消费者问题。

与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在

Java中怎

么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞

队列实现

的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。

33.用Java编程一个会导致死锁的程序,你将怎么解决?

这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非

常普遍,

但是很多侯选者并不能写deadlockfreecode(无死锁代码?),他们很挣扎。

只要告诉他

们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简

单这里的

n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来

得到关于

死锁的更多信息。

34.什么是原子操作,Java中的原子操作是什么?

非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。

35.Java中的volatNe关键是什么作用?怎样使用它?在Java中它跟

synchronized方法有什么不同?

自从Java5和Java内存模型改变以后,基于volatile关键字的线程问题越来越

流行。应该

准备好回答关于volatile变量怎样在并发环境中确保可见性。

36.什么是竞争条件?你怎样发现和解决竞争?

这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到

的竞争条

件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码

的竞争条

件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java

线程面试

问题之一,它可以确切的检测候选者解决竞争条件的经验,orwritingcodewhich

isfreeof

dataraceoranyotherracecondition0关于这方面最好的书是《Concurrency

practicesin

Java》o

37.你将如何使用threaddump?你将如何分析Threaddump?

在UNIX中你可以使用kill-3,然后threaddump将会打印日志,在windows中

你可以使

用"CTRL+Break"。非常简单和专业的线程面试问题,但是如果他问你怎样分析

它,就会很

棘手。

38.为什么我们调用start。方法时会执行run()方法,为什么我们不能直

接调用run()方法?

这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的

困惑。现

在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题

的回答应

该是这样的,当你调用start。方法时你将创建新的线程,并且执行在run()方法里

的代码。

但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代

码。阅读我

之前写的《start与run方法的区别》这篇文章来获得更多信息。

39.Java中你怎样唤醒一个阻塞的线程?

这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了10阻

塞,我并

且不认为有一种方法可以中止线程。如果线程因为调用wait。、sleep。、或者

join。方法而

导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。

我之前写的

《Howtodealwithblockingmethodsinjava》有很多关于处理线程阻塞的信

息。

40.在Java中CycliBarriar和CountdownLatch有什么区别?

这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是

CyclicBarrier可

以重复使用已经通过的障碍,而CountdownLatch不能重复使用。

41.什么是不可变对象,它对写并发应用有什么帮助?

另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java

面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么

String是不可变

的。

42.你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?

多线程和并发程序中常遇到的有Memory-interface.竞争条件、死锁、活锁和饥

饿。问题

是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而

不是基于

实际应用的Java线程问题

43、开启线程的三种方式?

1)继承Thread类,重写run()方法,在run()方法体中编写要完成的任务new

Thread().start();

2)实现Runnable接口,实现run()方法newThread(newMyRunnable()).start();

3)实现Callable接口MyCallable类,实现call。方法,使用FutureTask类来包装

Callable对象使用FutureTask对象作为Thread对象的target创建并启动线程;调

用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

FutureTask<lnteger>ft=newFutureTask<lnteger>(newMyCallableO);

newThread(ft).start();

44、run()和start。方法区别

run()方法只是线程的主体方法,和普通方法一样,不会创建新的线程。只有调用

start。方法,才会启动一个新的线程,新线程才会调用run()方法,线程才会开始

执行。

45、如何控制某个方法允许并发访问线程的个数?

创建Semaphore变量,Semaphoresemaphore=newSemaphore(5,true);当方

法进入时,请求一个信号,如果信号被用完则等待,方法运行完,释放一个信

号,释放的信号新的线程就可以使用。

46、在Java中wait和seelp方法的不同

wait。方法属于Object类,调用该方法时,线程会放弃对象锁,只有该对象调用

notify。方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

sleep。方法属于Thread类,sleep。导致程序暂停执行指定的时间,让出CPU,但

它的监控状态依然保存着,当指定时间到了又会回到运行状态,sleep。方法中线

程不会释放对象锁。

47、谈谈wait/notify关键字的理解

notify:唤醒在此对象监视器上等待的单个线程

notifyAHO:通知所有等待该竞争资源的线程

wait:释放。bj的锁,导致当前的线程等待,直接其他线程调用此对象的notify。或

notifyAHO方法

当要调用wait。或notify()/notifyAII()方法时,一定要对竞争资源进行加锁,一般放

到synchronized(obj)代码中。当调用obj.notify/notifyAII后,调用线程依旧持有

。均锁,因此等待线程虽被唤醒,但仍无法获得obj锁,直到调用线程退出

synchronized块,释放。bj锁后,其他等待线程才有机会获得锁继续执行。

48、什么导致线程阻塞?

(1)一般线程阻塞

1)线程执行了Thread.sleep(intmillsecond)方法,放弃CPU,睡眠一段时间,—

段时间过后恢复执行;

2)线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等到

获取到同步锁,才能恢复执行;

3)线程执行了一个对象的wait。方法,直接进入阻塞态,等待其他线程执行

notify()/notifyAII()操作;

4)线程执行某些10操作,因为等待相关资源而进入了阻塞态,如System.in,但

没有收到键盘的输入,则进入阻塞态。

5)线程礼让,Thread.yieldO方法,暂停当前正在执行的线程对象,把执行机会让

给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行

态,随时可能再次分得CPU时间。线程自闭,join。方法,在当前线程调用另一个

线程的join。方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前线程

再由阻塞转为就绪态。

49、线程如何关闭?

1)使用标志位

2)使用stop。方法,但该方法就像关掉电脑电源一样,可能会发生预料不到的问

3)使用中断interrupt。

publicclassThread{

II中断当前线程

publicvoidinterruptO;

II判断当前线程是否被中断

publicboolenislnterrupt();

II清除当前线程的中断状态,并返回之前的值

publicstaticbooleninterruptedO;

}

但调用interrupt。方法只是传递中断请求消息,并不代表要立马停止目标线程。

50、讲一下java中的同步的方法

之所以需要同步,因为在多线程并发控制,当多个线程同时操作一个可共享的资

源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确

保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一一,性和准确

性。

1)synchronized修饰同步代码块或方法

由于java的每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个

方法。在调用该方法前,需获得内置锁,否则就处于阴塞状态。

2)volatile修饰变量

保证变量在线程间的可见性,每次线程要访问volatile修饰的变量时都从内存中读

取,而不缓存中,这样每个线程访问到的变量都是一样的。且使用内存屏障。

3)ReentrantLock重入锁,它常用的方法有ReentrantLock():创建一个

ReentrantLock实例

lock。获得锁unlock。释放锁

4)使用局部变量ThreadLocal实现线程同步,每个线程都会保存一份该变量的副

本,副本之间相互独立,这样每个线程都可以随意修改自己的副本,而不影响其

他线程。常用方法ThreadLocal。创建一个线程本地变量;get()返回此线程局部的

当前线程副本变量;initialValueO返回此线程局部变量的当前线程的初始值;

set(Tvalue)将此线程变量的当前线程副本中的值设置为value

5)使用原子变量,如Atomiclnteger,常用方法Atomiclnteger(intvalue)创建个有

给定初始值的Atomiclnteger整数;addAndGet(intdata)以原子方式将给定值与当

前值相加

6)使用阻塞队列实现线程同步LinkedBlockingQueue<E>

51、如何保证线程安全?

线程安全性体现在三方法:

1)原子性:提供互斥访问,同一时刻只能有一个线和至数据进行操作。

JDK中提供了很多atomic类,5nAtomiclnteger\AtomicBoolean\AtomicLong,它

们是通过CAS完成原子性。JDK提供锁分为两种:synchronized依赖JVM实现

锁,该关键字作用对象的作用范围内同一时刻只能有一个线程进行操作。另一种

是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性是ReentrantLock。

2)可见性:一个线程对主内存的修改及时被其他线程看到。

JVM提供了synchronized和volatile,volatile的可见性是通过内存屏障和禁止重排

序实现的,volatile会在写操作时,在写操作后加一条store屏障指令,将本地内存

中的共享变量值刷新到主内存;会在读操作时,在读操作前加一条load指令,从

内存中读取共享变量。

3)有序性:指令没有被编译器重排序。

可通过volatile、synchronizedxLock保证有序性。

52、Java中对象的生命周期

1)创建阶段(Created):为对象分配存储空间,开始构造对象,从超类到子类

对static成员初始化;超类成员变量按顺序初始化,递归调用超类的构造方法,子

类成员变量按顺序初始化,子类构造方法调用。

2)应用阶段(InUse):对象至少被一个强引用持有着。

3)不可见阶段(Invisible):程序运行已超出对象作用域

4)不可达阶段(Unreachable):该对象不再被强引用所持有

5)收集阶段(Collected):假设该对象重写了finalize。方法且未执行过,会去执

行该方法。

6)终结阶段(Finalized):对象运行完finalize。方法仍处于不可达状态,等待垃

圾回收器对该对象空间进行回收。

7)对象空间重新分配阶段(De-allocated):垃圾回收器对该对象所占用的内存

空间进行回收或再分配,该对象彻底消失。

53、staticsynchronized方法的多线程访问和作用

staticsynchronized控制的是类的所有实例访问,不管new了多少对象,只有一

份,所以对该类的所有对象都加了锁。限制多线程中该类的所有实例同时访问

JVM中该类对应的代码。

54、同一个类里面两个synchronized方法,两个线程同时访问的问题

如果synchronized修饰的是静态方法,锁的是当前类的class对象,进入同步代码

前要获得当前类对象的锁;

普通方法,锁的是当前实例对象,进入同步代码前要获得的是当前实例的锁;

同步代码块,锁的是括号里面的对象,对给定的对象加锁,进入同步代码块库前

要获得给定对象锁;

如果两个线程访问同一个对象的synchronized方法,会出现竞争,如果是不同对

象,则不会相互影响。

55、volatile的原理

有volatile变量修饰的共享变量进行写操作的时候会多一条汇编代码,lockaddl

$0x0,lock前缀的指令在多核处理器下会将当前处理器缓存行的数据会写回到系

统内存,这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无

效。同时lock前缀也相当于一个内存屏障,对内存操作顺序进行了限制。

56、synchronized原理

synchronized通过对象的对象头(markword)来实现锁机制,java每个对象都有对

象头,都可以为synchronized实现提供基础,都可以作为锁对象,在字节码层面

synchronized块是通过插入monitorentermonitorexit完成同步的。持有monitor

对象,通过进入、退出这个Monitor对象来实现锁机制。

57、谈谈NIO的理解

NIO(NewInput/Output)引入了一种基于通道和缓冲区的I/O方式,它可以使用

Native函数库直接分配堆外内存,然后通过一个存储在Java堆的

DirectByteBuffer对象作为这块内存的引用进行操作,避免了在Java堆和Native

堆中来回复制数据。NIO是一种同步非阻塞的10模型。同步是指线程不断轮询

10事件是否就绪,非阻塞是指线程在等待10的时候,可以同时做其他任务。同步

的核心就是Selector,Selector代替了线程本身轮询10事件,避免了阻塞同时减

少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当10事件就绪时,可

以通过写道缓冲区,保证10的成功,而无需线程阻塞式地等待。

-synchronized和volatile关键字的区别

-synchronized与Lock的区别

-ReentrantLock、synchronized和volatile比较

1)volatile:解决变量在多个线程间的可见性,但不能保证原子性,只能用于修饰

变量,不会发生阻塞。volatile能屏蔽编译指令重排,不会把其后面的指令排到内

存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。多用于并行计算

的单例模式。volatile规定CPU每次都必须从内存读取数据,不能从CPU缓存中读

取,保证了多线程在多CPU计算中永远拿到的都是最新的值。

2)synchronized:互斥锁,操作互斥,并发线程过来,串行获得锁,串行执行代

码。解决的是多个线程间访问共享资源的同步性,可保证原子性,也可间接保证

可见性,因为它会将私有内存和公有内存中的数据做同步。可用来修饰方法、代

码块。会出现阻塞。synchronized发生异常时,会自动释放线程占有的锁,因此

不会导致死锁现象发生。非公平锁,每次都是相互争抢资源。

3)lock是一个接口,而synchronized是java中的关键字,synchronized是内置语

言的实现。lock可以让等待锁的线程响应中断。在发生异常时,如果没有主动通过

unLockO去释放锁,则可能造成死锁现象,因此使用Lock时需要在finally块中释放

锁。

4)ReentrantLock可重入锁,锁的分配机制是基于线程的分配,而不是基于方法

调用的分配。ReentrantLock有tryLock方法,如果锁被其他线程持有,返回

false,可避免形成死锁。对代码加锁的颗粒会更小,更节省资源,提高代码性

能。ReentrantLock可实现公平锁和非公平锁,公平锁就是先来的先获取资源。

ReentrantReadWriteLock用于读多写少的场合,且读不需要互斥场景。

-ReentrantLock的内部实现

-lock原理

-怎么避免死锁?

-对象锁和类锁是否会互相影响?

-什么是线程池,如何使用?

-Java的并发、多线程、线程模型

-谈谈对多线程的理解

-多线程有什么要注意的问题?

-谈谈你对并发编程的理解并举例说明

-谈谈你对多线程同步机制的理解?

-如何保证多线程读写文件的安全?

-多线程断点续传原理

-断点续传的实现

58.面试必备之乐观锁与悲观锁

何谓悲观锁与乐观锁

乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生

活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以

场景而定说一种人好于另外一种人。

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿

数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资

源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线

程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁

等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和

ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上

锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以

使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提

高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐

观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了

乐观锁的一种实现方式CAS实现的。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一

种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的

时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的

情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反

倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

乐观锁常见的两种实现方式

乐观锁一般会使用版本号机制或CAS算法实现。1.版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次

数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数

据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当

前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

举一个简单的例子:假设数据库中帐户信息表中有一个version字段,当前值

为1;而当前帐户余额字段(balance)为$100。

1.操作员A此时将其读出(versions),并从其帐户余额中扣除$50

($100-$50)0

2.在操作员A操作的过程中,操作员B也读入此用户信息(

version=1),并从其帐户余额中扣除$20($100-$20)。

3.操作员A完成了修改工作,将数据版本号加一(v

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论