Java笔试面试之多线程与并发

以下是笔者总结的Java笔试面试题中多线程与并发部分,答案为笔者自己总结得出,未必完全正确,仅供参考

1.多线程发生死锁原因及如何避免死锁

死锁是指多个线程因竞争资源而造成的一种僵局(互相等待)状态,若无外力作用,这些进程都将无法继续向前推进

死锁产生的条件:
(1) 互斥条件。进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
(2) 不剥夺条件。进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放,只能是主动释放
(3) 请求和保持条件。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
(4) 循环等待条件。存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求

避免死锁:
(1) 加锁顺序。线程按照一定的顺序加锁和释放锁
(2) 加锁时限。线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁
(3) 死锁检测

2.实现多线程的方法

(1) 通过继承Thread类并且重写run()方法
(2) 通过实现Runnable接口并且实现run()方法。这是最常用的方法,相比第一种,这种可以在继承了其它类的情况下实现多线程
(3) 通过实现Callable接口并实现call()方法。这种方法有返回值,并且可以抛出异常
(4) 通过线程池创建线程

3.run()方法与start()方法的区别

(1) 通过调用Thread类的start()方法启动一个线程。run()方法完成了线程的实际操作
(2) 通过start()方法启动线程可以达到多线程异步的效果。直接调用run方法是同步的,达不到多线程的效果

4.sleep()方法与wait()方法的区别

(1) sleep()是Thread类中的方法。wait()是Object类中的方法
(2) sleep()方法可以放在任何地方使用。wait()方法在多线程的情况下使用,而且要用在synchronized修饰的方法或代码块中
(3) sleep()方法被调用时,让线程暂停一段时间,并把cpu执行权让给其它线程,但是它不释放锁,过了指定时间自动唤醒。wait()方法是让当前线程等待并释放锁,如果没有其它线程调用notify()或notifyAll()方法唤醒,那么它将一直等待

5.进程和线程的区别

(1) 进程是正在运行的应用程序,线程是进程内部的一个执行序列,可以认为线程是轻量级的进程
(2) 进程间是相互独立的,不同进程数据是不共享的。线程间的数据共享
(3) 进程间的切换开销比线程间的切换开销更大

6.synchronized与Lock有什么异同

两者都是用来实现对某个资源的同步,都属于重量级锁,都是可重入的
两者区别如下:
(1) 用法不一样。synchronized可以用于修饰方法,也可以修饰代码块。使用Lock锁则需要指定起始和终点位置,一般放在try-finally结构中,finally语句块必须要执行unlock()方法
(2) 性能不一样。在资源竞争不激烈情况下,两者性能相差不大,而在资源竞争激烈时,synchronized的性能下降很快,而Lock基本保持不变
(3) 锁机制不一样。synchronized获得锁和释放锁都是在块结构中,获取多个锁时必须以相反顺序释放,自动释放锁。Lock需要开发人员手动释放锁,并且无论是否发生异常都要释放锁

7.volatile和synchronized的区别

(1) volatitle修饰的是变量。synchronized修饰的是方法或代码块
(2) volatitle不会加锁,非阻塞的。synchronized会加锁,是阻塞的
(3) volatitle仅保证变量的可见性。synchronized即保证可见性,又保证原子性
(4) volatitle修饰的变量不会被编译器优化,即禁止指令重排。synchronized修饰的方法可以被编译器优化

8.线程池的优点

(1) 通过重复利用已创建的线程,减少在创建和销毁线程上所花的时间和系统开销
(2) 提高响应速度。任务可以不用等到线程创建就可以立即执行
(3) 提高线程的可管理性。可以对线程进行统一的分配和监控

9.线程池的工作原理

(1) 如果线程池中的线程数量小于核心线程数,直接创建新的线程来处理任务
(2) 如果线程池中的线程数量大于等于核心线程数,但是缓冲队列未满,任务放入缓冲队列,等待空闲线程取出去执行
(3) 如果线程池中的线程数量大于等于核心线程数,并且缓冲队列已满,但没有达到最大线程数,建立新的线程处理任务
(4) 如果线程池中线程数量达到最大线程数,通过指定的拒绝策略处理任务

10.线程池handler的拒绝策略

(1) AbortPolicy。不执行新任务,直接抛出异常,提示线程池已满
(2) DisCardPolicy。不执行新任务,也不抛出异常,直接丢弃任务
(3) DisCardOldSetPolicy。将队列中的最旧的任务丢弃并替换为当前新进来的任务并执行
(4) CallerRunsPolicy。不在线程池执行,直接调用execute()来执行当前任务

11.常见线程池

(1) CachedThreadPool,可缓存的线程池。该线程池中没有核心线程,非核心线程的数量无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况
(2) ScheduleThreadPool,周期性执行任务的线程池。有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务
(3) SingleThreadPool,单条线程池。只有一条线程,适用于有顺序的任务的应用场景
(4) FixedThreadPool,定长的线程池。有核心线程,核心线程的即为最大的线程数量,没有非核心线程

12.实现线程间通信方法

(1) 通过Object类的wait()/notify()/notifyAll()方法。在synchroized方法或代码块中使用
(2) 通过Condition的await()/signal()/signalAll()方法。在Lock的lock()和unlock()之间使用
(3) 通过管道流实现。一个线程发送数据到输出管道流(PipedOutputStream),另一个线程从输入管道流读取数据(PipedInputStream)。只适用两个线程间通信
(4) 使用volatile关键字修饰变量。使用volatile修饰的变量,更新对其它线程是立即可见

13.同步方法

(1) 通过synchroinzed关键字修饰方法或者代码块实现。修饰普通方法则默认把该对象当做锁,而修饰静态方法则是把类当成锁,修饰代码块可以自定义锁
(2) 通过volatile关键字修饰变量。可以保证可见性,即一个线程对数据的更新对其它线程是立即可见的。还可以禁止指令重排,也就是保证变量赋值的操作顺序和程序运行顺序一致
(3) 通过ReetrantLook进行同步。这种同步代码需要放在try-finally结构中,并且需要手动加锁和释放锁
(4) 通过CAS实现

14.什么是CAS

CAS是比较和交换,是项乐观锁技术。synchronized锁是阻塞算法的,而CAS是非阻塞算法的。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则就重试,直到成功为止

15.ABA问题

ABA问题是由CAS导致的,出现这种问题的原因是,线程1对值读取为A,这时线程2已经对值由A改成B然后又改回A了,当线程1对值进行修改时,就会认为值是没有改过的。解决的办法是加个版本号,每次更新都使版本号加1,每次进行CAS操作时对比版本号即可

16.CopyOnWriteArrayList的实现

CopyOnWrite即写时复制。往容器添加元素时,先把容器复制一份出来,并在新的容器执行写操作,添加完毕后将原容器的引用指向新容器。这是一种读写分离的实现,读操作不用加锁,写操作要加锁

17.同步辅助类

(1) CountDownLatch类。倒计时机制,一个线程等待的时候,必须等到其它线程执行完毕后才能执行,计时器的值无法重置
(2) CyclicBarrier类。多个线程相互等待到达某个公共屏障点才一起执行,计时器的值可以被重置
(3) Semaphore类。可以控制某个资源可以被同时访问的个数

CountDownLatch相当于过马路时,要等待一队人通过后才能通过
CyclicBarrier相当于举行活动时,等到足够的人到场后才能开始活动

18.阻塞队列

(1) ArrayBlockingQueue。基于数组有界缓存的阻塞队列,在读写操作上都要锁住整容器,生产者和消费者共用一把锁控制同步
(2) LinkedBlockingQueue。基于链表的阻塞队列,可有界也可以无界,生产者和消费者采用不同的锁控制同步
(3) PriorityBlockingQueue。基于数组无界阻塞队列,按优先级高低顺序进行出队,不会阻塞生产者,但会阻塞消费者
(4) DelayQueue。延迟无界阻塞队列,只有延迟期满才能取出元素
(5) SynchronousQueue。同步队列,只能存一个元素

19.多线程优点和缺点

优点:
(1) 充分利用cpu
(2) 程序响应更快
缺点:
(1) 上下文切换开销
(2) 增加资源消耗

20.多线程中锁的种类

(1) 可重入锁。当前线程已经获得某个监视器对象持有的锁,那么该线程在该方法中调用另一个同步方法也同样持有该锁。例如ReentrantLock和synchronized
(2) 可中断锁。可以响应中断的锁
(3) 公平锁。尽量以请求锁的顺序来获取锁,ReentrantLock和ReentrantReadWriteLock可以设置公平锁
(4) 读写锁。读读共享,读写互斥,写写互斥

21.锁优化

(1) 自适应自旋锁。线程等待时执行一个忙循环,避免线程间切换开销,自适应可以动态改变自旋的时间
(2) 锁消除。虚拟机编译器对同步代码上一些不存在共享数据竞争的锁消除
(3) 锁粗化。虚拟机检测到多个操作对同一对象加锁时,锁范围自动扩大
(4) 轻量级锁减少了在无竞争情况下的同步
(5) 偏向锁消除了数据在无竞争情况下的同步

22.守护线程

区别于用户线程,守护线程是在后台执行的,为其它线程提供服务。当用户线程运行结束时,守护线程自动结束。比如垃圾收集器就是守护线程。可以通过setDemon(true)方法设置一个线程为守护线程

23.线程状态

(1)创建状态。线程创建时的状态
(2)就绪状态。调用了线程的start()方法
(3)运行状态。cpu执行调度到当前线程,由就绪变为运行
(4)阻塞状态。线程运行过程被暂停
(5)死亡状态。线程执行结束或抛出异常

24.终止一个线程的方法

通过异常法终止线程执行。用isInterrupted()方法判断线程是否处于中断状态,如果是则抛出异常

25.ThreadLocal

ThreadLocal是一个容器,用于存放每个线程的局部变量。ThreadLocal实例是private static类型的

26.原子性、可见性和有序性

(1) 原子性。操作是不可分割的(比如i++这种可以分割的,也就是非原子性的)
(2) 可见性。一个线程对变量修改后,对其它线程是立即可知的
(3) 有序性。在本线程中观察,所有操作是有序的,而在其它线程观察本线程是无序的

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值