java多线程面试总结

Java多线程面试总结

1.     线程与进程   线程是操作系统能够运算调度的最小单位,被包括在进程之内,是进程的实际运作单元,线程是进程的子集,不同进程使用不同的内存空间,所有线程共享同一片内存空间。每个线程有独立的栈内存来存储本地数据。

2.     Java的内存模型,分为主内存和工作内存,主内存为线程共有,工作内存为线程私有,工作内存包括线程所要使用的变量的主内存拷贝,线程之间变量的共享,需要靠主内存来完成。JVM内存模型和JMM是两种概念,如果一定要比较的化,主内存大概就是堆内存中对象实例的数据部分,而工作内存大概就是虚拟机栈的部分区域,从底层来说,主内存就是物理机内存,为了更好的速度,虚拟机会将工作内存寄存于寄存器和高速缓存中。

3.     多线程导致线程安全的三大特性a.原子性 只操作要么不执行,要么执行完成,典型的非原子操作自增i++,分为三步读取数据,自增,写会内存。Violate不能保证原子性,同样是自增的例如果一个线程刚读取到公共变量,就被抢夺,另一个线程抢夺执行完之后,该线程拿读取到的数值继续自增,就导致结果有误。b.可见性 指的是但一个线程改变共有变量的值,其他线程可以马上看到。因为给变量赋值分为两步,先写在缓存中,再写入主内存,用violate关键字保证可见性,作用更新值之后会立即同步到主内存,读取前会从主内存读取。Sychronized保证可见性,释放锁之前一定会将值同步到主内存。c.有序性。处理器会在保证单线程执行结果不变的情况下(即对没有数据依赖性),对指令执行进行重排序。但在多线程中公共变量不同顺序可能会影响到其他线程的运行结果。Violate解决,violate修饰的变量进行读写时,会保证前面的更新已经完成,并且不会使这样的变量后面的语句重排序到前面去执行.

4.     线程不安全与不安全的类常见比较。HashMap与hashtable,vector与ArrayList,StringBuffer和StringBuilder

HashMap 底层是一个链表加数组的结构组成,基本存储单元是一个Entry数组,每个位置为一个单向链表,对k进行hash取模,相同的位置的以链表形式存储,当链表加入一个新元素时,会把新元素作为头节点,把原来的连在其后 ,当多线程时,一个线程刚变成头节点,后面的元素还没连上,就被抢了,另外一个元素变成头结点,前一个元素就丢失了。

ArrayList的线程不安全体现在在调用add方法时会调用一个方法判断是否需要扩容,当一个调用判断后,另一个线程再调用,会导致角标越界。另外elementData[size++]= e,也是一个非原子操作。,

StringBuilder 底层可以看错一个字符数组,append时,先对底层数组进行扩容,用一个count记录原有字符在底层数组中所在的位置,然后将新字符的字符数组复制到底层数组,当一个线程复制完成后count未更新,另一个数组继续根据

原有count进行更新,导致前一个更新的字符丢失。

5.     内存溢出与内存泄漏, 溢出值在申请资源时没有足够的内存可以用了,泄露指申请资源之后用完未归还。

6.     同步方法的锁对象是当前对象,是在方法上加锁,而同步代码块的锁对象可以是任意对象,是在方法内部加锁。Wait方法线程暂停释放锁,sleep,yield方法线程暂停但不是释放锁。同步锁Lock大致功能与上两种一致,但还包括一些其他的功能,如使用非块结构的tryLock()方法,以及试图获取可中断锁的lockInterruptibly()方法,还有获取超时失效锁的tryLock(long,TimeUnit)方法。

7.     基础 线程的几种状态 新建(线程对象被创建),就绪状态(调用了start()方法等待cpu的调度),运行状态,阻塞状态(sleep,join),死亡状态。

8.     Yield会使一个方法从运行状态转换为可运行状态,而不是等待或阻塞,但不会释放锁,join会是一个线程阻塞到另一个线程完成之后再执行,sleep开始睡眠但不会释放锁,wait方法使线程释放锁进入阻塞,直到调用notify方法执行完同步代码释放锁唤醒再进入可运行状态。(注意生产者消费者模式)。

9.     死锁。定义是因为由于多个线程竞争资源所导致的一种相互等待。死锁产生的条件,1.互斥条件,一段时间,某种资源只能被一个线程所占有,其他线程只能等待。2.不剥夺条件,资源不能被强行夺走,只能靠线程执行完自行释放。3.请求和保持条件,线程已经保持了一个资源,有请求另一个资源,,而该资源被另外线程占有,请求阻塞,但又不释放自身的资源,循环等待链,每一个线程所持有的资源又被下一个线程锁请求。

10.  避免死锁 1.加锁顺序 多个线程需要同一些锁,如果按不同顺序加锁,就容易死,如果按相同的顺序获得,可以避免。

         2.加锁时限 请求获得某个锁超过一定时限,就回退释放已获得的所有锁,过一段时间再尝试

 3.死锁检验  每一个线程获得锁 会在一个数据结构中记下来,如果请求另一个锁失败,会遍历之前的记录,判断是否死锁。然后可以全部回退或者设置优先级。

11.  为什么要使用线程池,创建和销毁线程伴随着系统开销,频繁的创建销毁,很大程度影像系统的效率。线程并发数量过多,会抢占系统资源而导致阻塞。可以对线程进行一些简答的管理。四种常见的线程池CachedThreadPool,新任务来了就分配新线程,如果线程池中线程空闲超过一定时间,就会就会回收, FixedThreadPool 指定线程数的线程池。singleThreadPool一个核心线程,任务排队执行。scheduleThreadPool大小无限,支持定时和周期性执行线程。

12.  一个线程池包括以下四个基本组成部分:
               1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

               2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

               3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

               4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

13.   ThreadPoolExecutor的完整构造方法的签名是:ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler) .

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize-池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

14.ThreadLocal 指的是线程局部变量。 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。Thread类中有一个成员变量ThreadLocalMap,当调用ThreadLocal的set方法,即把key是ThreadLocal类的实例对象,v是每个用户的值放入这个ThreadLocalMap。 一个共享的对象直接保存到ThreadLocal中,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。所以要在保存到ThreadLocal之前,通过克隆或者new来创建新的对象,然后再进行保存. 作用:提供一个线程内公共变量(比如本次请求的用户信息),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响. ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap-> Entry -> value
永远无法回收,造成内存泄露。要调用ThreadLocalMap的getEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值