在看完《Java多线程编程核心技术》
与《Java并发编程的艺术》
之后,对于多线程的理解到了新的境界. 先拿如下的题目试试手把.
Q1: 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行? 答案: 使用Thread.join()方法即可.当然JUC包内提供了
CountDownLatch
与CyclicBarrier
工具类供我们选择. 如果我是面试官, 我会进行深入询问. Q: 什么是CountDownLatch
?什么是CyclicBarrier
?两者的区别? A: 可以重置, 接口类型不同. Q: 再深入一点,实现的机制? A: 使用锁的Condition进行完成 Q:Condition的实现机制 -> A: AQS ->CAS .刨根问底总是会将问题复杂化.Q2: Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。 A2-1:
Lock
相比与synchronized
在使用时更加的灵活.Lock
的底层实现使用的是AQS -> CAS
.会更加高效.Lock
实现了共享锁与独占锁两种机制.AQS
自定义实现Lock
.而synchronized
关键字则较为难以更改.Lock
,可以创建不同的Condition
.以用于不同的唤醒工作.这是synchronized
的wait/notify
难以实现的.Lock
的实现AQS
.A2-2: 保证数据完整性与高性能缓存是两个问题.
ReentReadWriteLock
.读锁共享,写锁互斥.读读共享,写写/写读互斥.
ConcurrentHashMap -> Segment -> HashEntry
的类型结构.反例HashTable
与SynchronizedMap
ReentReadWriteLock
的基本实现. 我的话会将其与数据库内的读写操作进行询问.(行级锁 -> 表级锁 -> Mysql内优化 )
高性能的深入只要掌握ConcurrentHashMap
数据结构即可.
Q3: Java 中 wait 和 sleep 方法有什么区别? A: wait 与 sleep都是线程等待. 值得一提的是, wait与sleep都会使当前线程处于阻塞状态.不同点在于:
Q4: 如何在 Java 中实现一个阻塞队列? A: 实现阻塞队列之前先要理解什么是阻塞队列?
FIFO
的特性即可.class Test{ ArrayList list; volatile int count; Lock lock; Condition fullCondition; Condition emptyCondition; public Test(){ list = new CopyOnWriteArrayList(); count = 0; lock = new ReentrantLock(); fullCondition = lock.newCondition; emptyCondition = lock.newCondition; } // 弹出队列 public void offer(){ try{ lock.lock(); while(count == 0){ emptyCondition.await(); } list.get(i); count--; }finally{ lock.unlock(); } // 压入队列 public void offer(int var){ try{ lock.lock(); while(count == list.size()){ fullCondition.await(); } list.add(var); count++; }finally{ lock.unlock(); } } }
Q5: 如何在 Java 中编写代码解决生产者消费者问题? A: 生产者与消费者问题.非常类似上方的阻塞队列.这里提供一个使用
LinkedBlockingQueue
实现的生产者与消费者. import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class ProducerConsumerSolution { public static void main(String[] args) { BlockingQueue<Integer> sharedQ = new LinkedBlockingQueue<Integer>(); Producer p = new Producer(sharedQ); Consumer c = new Consumer(sharedQ); Consumer c2 = new Consumer(sharedQ); p.start(); c.start(); c2.start(); } } class Producer extends Thread { private BlockingQueue<Integer> sharedQueue; public Producer(BlockingQueue<Integer> aQueue) { super("PRODUCER"); this.sharedQueue = aQueue; } public void run() { // no synchronization needed for (int i = 0; i < 10; i++) { try { System.out.println(getName() + " produced " + i); sharedQueue.put(i); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Consumer extends Thread { private BlockingQueue<Integer> sharedQueue; public Consumer(BlockingQueue<Integer> aQueue) { super("CONSUMER"); this.sharedQueue = aQueue; } public void run() { try { while (true) { Integer item = sharedQueue.take(); System.out.println(Thread.currentThread().getName() + " consumed " + item); } } catch (InterruptedException e) { e.printStackTrace(); } } } 因为上述的代码内直接使用了LinkedBlockingQueue
是线程安全的.所以不需要更多的进行处理. Q5-2: 深入,LinkedBlockingQueue
的实现原理.见上.LinkedBlockingQueue
的读数据和取数据的操作都是需要加锁的. Q5-3: 是否有使用过其他的线程安全集合类?ConcurrentHashMap
的读操作和写操作都需要加锁么?ConcurrentLinkedQueue
呢? A5-3:ConcurrentHashMap
的读操作不加锁.使用的是volatile
变量.ConcurrentLinkedQueue
读操作和写操作都不加锁.使用CAS
进行操作.Q6: 写一段死锁代码。你在 Java 中如何解决死锁? A6-1: 死锁发生是因为相互资源等待,而不释放自身的锁资源.举个例子 class ThreadA extends Thread{ Lock lockA; Lock lockB; public void run(){ lockA.lock(); Thread.sleep(1000); lockB.lock(); lockA.unlock(); lockB.unlock(); } } class ThreadB extends Thread{ Lock lockA; Lock lockB; public void run(){ lockB.lock(); Thread.sleep(1000); lockA.lock(); lockB.unlock(); lockA.unlock(); } } 可以看到上述的线程,
Java
, 我们一般使用tryLock(long time)
.主要处理请求和保持条件
. Q6-2: 深入会问哲学家就餐问题
和银行家算法
?
A6-2:
哲学家就餐问题
:5个哲学家6只筷子.
解决措施:AND
策略,当获取左右2只筷子才进食.一次性获取所有的锁./记录策略
4个哲学家拿筷子,这样至少一个人可以进食.记录策略
奇偶排序, 5个人都先争取奇数筷子, 再争取偶数筷子.Q7-1: 什么是原子操作?Java 中有哪些原子操作? A1: 原子操作是指在
Java
执行过程中, 要么全部成功, 要么全不成功.Java
内一共提供了13种原子操作.原子操作的原理是CAS
. Q7-2: 你需要同步原子操作吗? A2: 不需要同步原子操作. 原子操作是通过CAS
进行控制的.CAS
根据操作系统底层的不同而不同.例如Linux
系统的底层脚本与Windows
系统的底层脚本就不一样.Q8: Java 中 volatile 关键字是什么?你如何使用它?它和 Java 中的同步方法有什么区别? A8:
volatile
关键字是将线程内的局部变量与进程内的公共变量同步.(JMM模型)可见性 / 一致性
-线程局部变量与进程变量共享 /有序性
–happen-before
原则, 使被volatile
关键字修饰的变量不会进行重排序. Java开发中的volatile你必须要了解一下Q9: 什么是竞态条件?你如何发现并解决竞态条件? A9: 竞态条件非常简单, 两个线程同时竞争同一个资源变量. 举个最简单的例子: class CompareThread extends Thread{ public int count; public CompareThread(int count){this.count = count;} public void run(){count++;} } 当启动两个线程的时候,
count++
不一定是需要的值.
Q10: 在 Java 中你如何转储线程(thread dump)?如何分析它? 通过
jstack -l <pid>
即可. 分析: 直接阅读.或者使用相应的分析工具.Q11: 既然 start() 方法会调用 run() 方法,为什么我们调用 start() 方法,而不直接调用 run() 方法? A11:
start()
方法在另启动一个子线程进行执行.run()
方法不会启动子线程,而是在当前线程后顺序执行.Q12: Java 中你如何唤醒阻塞线程? A12:
sleep()
方法的阻塞,等待其时间到了即唤醒.join()
方法的阻塞, 当其join()
的线程运行完毕后即会唤醒.wait()
方法的阻塞, 当其notify()
的时候即会唤醒.IO
资源等问题的阻塞, 当资源获取后即会唤醒.Q13: Java 中 CyclicBarriar 和 CountdownLatch 有什么区别?
CountdownLatch
的屏障点不可以重置, CyclicBarriar
可以重置.CountdownLatch
当await()
结束后;CyclicBarrier
可以在构造函数时,指定屏障打开后的运行线程Runnable
.Q14: 什么是不可变类?它对于编写并发应用有何帮助? A: 不可变类应当是
final
修饰的类.无法被继承. Q14-1: 深入:String
类型是不可变类. JVM的常量池.Q15: 你在多线程环境中遇到的最多的问题是什么?你如何解决的? A15: 就个人而言, 多线程遇到最多的是资源的调优与使用. 包括数据库线程池.
Spark
内的每个Executor
获取的资源数目. 内存干扰、竞态条件、死锁、活锁、线程饥饿是多线程和并发编程中比较有代表性的问题。这类问题无休无止,而且难于定位和调试。 这是基于经验给出的 Java 面试题。你可以看看Java 并发实战课程来了解现实生活中高性能多线程应用所面临的问题。Q16: 线程和进程的区别? A16: 两者都是单位. 线程是操作系统的任务单位. 而线程是进程的子单位. 我们操作系统的应用通常就是一个进程.在应用内,还有许多的子线程.
Q17: 多线程的上下文切换是什么? A17: 多个线程因时间片使用完而造成的运行程序上下问直接的切换.举个例子: 线程A -> 线程B -> 线程A
Q18: 死锁和活锁的区别?死锁和饥饿的区别? A18: 活锁即我们常用的锁. 死锁是获取不到锁而是当前线程造成的死循环.死锁会造成资源的大量消耗及线程阻塞.
Q19: Java 中使用什么线程调度算法? A19: FIFO / 时间片轮转 linux进程/线程调度策略(SCHED_OTHER,SCHED_FIFO,SCHED_RR)
Q20: 线程中如何处理某个未处理异常? A20:
try-catch
. 设置默认异常处理器UncaughtExceptionHandler
.Future
的Get
方法. 若无处理, 子线程会直接退出程序. Java子线程中的异常处理(通用)Q21: 什么是线程组?为什么 Java 中不建议使用线程组? A21:
ThreadGroup
.
stop()/resume()
等方法已被废弃.Q22: 为什么使用 Executor 框架比直接创建线程要好? A22:
Executor
内的基本类与基本组成么?Q23: Java 中 Executor 和 Executors 的区别? A23:
Executor
接口,主要接口方法为execute()
;常用的是ExecutorService
, 主要接口为submit()/shutdown()/isShutDown()
.Executors
静态类, 主要是用于创建线程池Executors.newFixedThreadPool(4)
.Q24: 在 windows 和 linux 系统上分别如何找到占用 CPU 最多的线程? A24: Linux.使用
top
命令即可. Windows. 使用任务管理器.
进程和线程是两个单位.进程通常是我们说的运行程序,是相对于操作系统而言的,通常可以使用ps -ef / jps
进行查询得出.而线程,通常称为子线程,也就是一个进程能够分为一个或多个子线程.线程通常是为提升进程的效率而设定的.
在一个进程中,我们同时开启多个线程,让多个线程去完成某些任务.(比如后台服务,就可以用多个线程响应多个客户请求.)
时间片轮转.
实现Runnable
接口和继承Thread
类.
thread.start()
和thread.run()
方法有什么区别?start()
方法会启动子线程,及新线程运行run()
方法;
run()
方法,不会生成子线程(子线程)进行运行;
PS: 2019-06-12已经更正. 谢谢博友指证. 其实记也好记, run()
其实直接调用Thread类中重写的run()
方法, start()
是新启动一个子线程, 运行run()
方法.
synchronized
关键字?
synchronized
缺陷?x
程序阻塞.如何才会释放?效率低下.
Lock在一定时间内未获取,会自动进行释放;
Lock在使用wait/notify
的时候,可以使用不同的Condition
进行控制唤醒的进程;
Lock可以将读锁
和写锁
进行分离,提升系统的运行效率.
runnable
与callable
.线程的回调函数.
[1] Java面试:投行的15个多线程和并发面试题 [2] 40个Java多线程问题总结
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/174555.html原文链接:https://javaforall.cn