java并发面试题(二)实战
本文列出了在工作中会用到的并发编程的实战问题,大家可以一起交流下,在回复中给出答案。
并发容器和框架
- 如何让一段程序并发的执行,并最终汇总结果?
- 如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取最好的吞吐量?
- 如何使用阻塞队列实现一个生产者和消费者模型?请写代码。
- 多读少写的场景应该使用哪个并发容器,为什么使用它?比如你做了一个搜索引擎,搜索引擎每次搜索前需要判断搜索关键词是否在黑名单里,黑名单每天更新一次。
Java中的锁
- 如何实现乐观锁(CAS)?如何避免ABA问题?
- 读写锁可以用于什么应用场景?
- 什么时候应该使用可重入锁?
- 什么场景下可以使用volatile替换synchronized?
并发工具
- 如何实现一个流控程序,用于控制请求的调用次数?
答案
可以阅读以下参考资料,知道答案后可以在回复中交流
- Java线程池的分析和使用 Java线程池(第二题)
- 原子操作的实现原理 (锁 第一题)
- Java中的读写锁(锁 第二题)
- 如何设计客户端流控程序 (并发工具 第一题)
- 待续
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: java并发面试题(二)实战
有些题目还真不会哟
问题1:
a.切分问题。
b.每个线程执行的结果存入一个容器。
c.汇总结果,做进一步处理。
================================
很多问题不太清楚,没实战过。悲催~~~
持续关注!
看看FutureTask
Future这个只是一个线程池吧,特殊点是它可以获取到任务的一个返回结果。
使用countDownLatch
Fork/Join值得关注
1. 使用CyclicBarrier 和CountDownLatch都可以
使用CyclicBarrier 在多个关口处将多个线程执行结果汇总
CountDownLatch 在各线程执行完毕后向总线程汇报结果
2. 配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
有界队列和无界队列的配置需区分业务场景,一般情况下配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列。
任务非常多时,使用非阻塞队列使用cas操作替代锁可以获得好的吞吐量。
4. CopyOnWriteArrayList这个容器适用于多读少写…读写并不是在同一个对象上。在写时会大面积复制数组,所以写的性能差,在写完成后将读的引用改为执行写的对象
1. 读取内存值
比较内存值和期望值
替换内存值为要替换值
带参数版本来避免aba问题,在读取和替换的时候进行判定版本是否一致
2. 多读少写,读写锁支持多个读操作并发执行,写操作只能由一个线程来操作
3. 重入锁指的是在某一个线程中可以多次获得同一把锁,在线程中多次操作有锁的方法。
4. 只需要保证共享资源的可见性的时候可以使用volatile替代,synchronized保证可操作的原子性一致性和可见性。
volatile适用于新值不依赖于就值的情形。
回答的非常好。
第一问用ForkJoinPool的子类RecursiveTask应该就可以吧???
1 可以用join,或者条件变量
2 CPU密集型的CPU个数+1,IO密集型的需要调优,看特定环境,处理快的可以是无界,linkedBlockingQueue吧
3 生产者负责notify,消费者需要wait,条件是有无元素
4 Copyonwrite或者CAS型
1 利用CPU的cas命令,避免ABA需要使用类似于version,AtomicStampedReference也可以
2 读多写少
3 需要使用除了内置锁以外的锁特性,比如可中断,可等待的锁,平等锁等
4 1写N读
1 fixed的线程池
回答得不错。
一天一次的更新需要加锁吗?new新对象出来替换掉老的就好
一天一次的更新需要加锁吗?new新对象出来替换掉老的就好。
1.CyclicBarrier CountDownLatch join
2. 配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数 有界队列好,无界队列不可控,有可能导致内存不够 synchronousQueue吞吐率最高
3 BlockingQueue put take
4CopyOnWriteArrayList这个容器适用于多读少写 读的时候可以不用加锁
1 compareAndSet AtomicStampedReference
2多读少写
3可轮询,可中断,定时,非块,公平队列等高级特性时候使用可重入锁
4 单线程修改变量或不依赖当前值,且不与其他变量构成不变性条件时候使用volatile
什么场景下可以使用volatile替换synchronized
volatile的应用场景一句话概括下来就是,对于共享资源,要做到读不加锁,写加锁,那么就必须使用volatile,这里的写加锁可以使用互斥锁,也可以使用CAS的乐观锁机制
如何让一段程序并发的执行,并最终汇总结果?
1. 可以使用Callable+FutureTask+Executors+Future操作,让收集结果的线程阻塞等待与get()方法等待结果
2. 可以使用Thread的join方法来阻塞等待结果
3. 可以使用CountDownLatch来让收集结果的线程等待
4. 可以使用CyclicBarrier来收让收集结果的线程等待,
CountDownLatch是CyclicBarrier的一种,所以CountDownLatch能解决的问题,CyclicBarrier一定可以
如何使用阻塞队列实现一个生产者和消费者模型?请写代码
使用基于数组的阻塞队列,有限次取水果和放水果
package com.tom.jdk5.concurrent.collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/*************************************
* 使用阻塞队列实现生产者消费者问题
*
* BlockingQueue的offer/poll操作不能满足阻塞等待的效果
*
*
*************************************/
class Plate {
// 一个盘子,可以放10个水果
private BlockingQueue fruits = new LinkedBlockingQueue(10);
// 如果有水果,则取得,否则取不走
public String get() {
try {
return fruits.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
public void put(String fruit) {
try {
fruits.put(fruit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Producer implements Runnable {
private Plate plate;
public Producer(Plate p) {
this.plate = p;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
this.plate.put("" + i);
System.out.println("第" + i + "个水果放入盘子");
Thread.sleep((long) (200 * Math.random()));
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
private Plate plate;
public Consumer(Plate p) {
this.plate = p;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
String j = this.plate.get();
System.out.println("第" + j + "个水果取出盘子");
Thread.sleep((long) (400 * Math.random()));
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ProducerConsumerTest {
public static void main(String[] args) {
Plate p = new Plate();
Producer producer = new Producer(p);
Consumer consumer = new Consumer(p);
new Thread(producer).start();
new Thread(consumer).start();
}
}
如何实现乐观锁(CAS)?如何避免ABA问题?
CAS原语有三个值,一个是内存值,一个是期望值,一个是写入值。 在不加锁的情况下写入时,每次读取内存值,然后跟预期值比对,如果比对失败,反复的读和比对,直到成功。在CAS原语是一个原子操作,如果写入时,内存值发生改变,则写入值失败
ABA问题可以使用AtomicStampedReference在做CAS操作时,一方面比较内存中的操作数与预期值是否一样,同时比较内存中的操作数的时间戳(或者修改次数)是否与预期值一样。如果本次修改操作成功,一定要修改操作数的时间戳,可以通过每次加1的方式。
读写锁可以用于什么应用场景?
读写锁适用于读操作>>写操作,同时允许多个线程同时读,读读允许,读写互斥,写写互斥。这样的场景比较常见,比如,一个文件,我们可以要求同时读,但是不能读写或者写写同时发生。缓存Cache也可以实现同时读,互斥写。对于lucene这样的文本检索系统,也可以使用读写锁实现更新索引文件上写锁,读取索引加读锁。
什么时候应该使用可重入锁?
1.公平锁
2.多路条件,比如我要实现5个线程的同步,A->B->C->D->E->A,可以使用一个Lock,5个Condition来控制5个线程的步调
3.实现轮询,中断的功能,只听说过,没用过。
这个没太明白
如何实现一个流控程序,用于控制请求的调用次数?
应该可以考虑使用Semaphore来控制, Semaphore本身是用来限制有限资源的争用。比如我要限制数据数据库连接池的连接数不超过20个,我可以在连接池的配置里面设置最大值,也可以在连接池的访问层来控制,这样可以减轻连接池的负担,也可以控制得不到连接的线程的行为,比如等待而不是扔个SQLException出来
赞楼上,都回答了啊。
谢谢博主,呵呵。最近在研究juc,看到这个好网站,所以就回答下,看看自己掌握多少。以后还等多学多用,如果上面回答的有什么问题,还望不吝批评指正啊。
我也来答一下,哈哈
一
1、可以用RecursiveTask来实现
2、CPU密集的话,根据实际需求减少线程数,IO密集的话,可以看读写的比重,读得多用CopyOnWrite,写的多用Concurrent,同意楼上,用linkedBlockingQueue
3、直接贴代码
class Producer extends Thread{
private BlockingQueue bg;
public Producer(BlockingQueue bg) {
this.bg = bg;
}
public void run() {
String[] strArr = new String[]
{
“1”,
“2”,
“3”
};
for(int i=0;i<999999999;i++) {
System.out.println(Thread.currentThread().getName() + "生产者准备生产的集合元素!");
try{
Thread.sleep(200);
//尝试放入元素,如果队列已满,则线程被阻塞
bg.put(strArr[i%3]);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "生产完成:" + bg);
}
}
}
class Consumer extends Thread {
private BlockingQueue bg;
public Consumer(BlockingQueue bg) {
this.bg = bg;
}
public void run() {
while(true) {
System.out.println(getName() + “消费者准备消费集合元素!”);
try {
Thread.sleep(200);
//尝试取出元素,如果队列已空,则线程被阻塞
bg.take();
}catch(Exception e) {
e.printStackTrace();
}
System.out.println(getName()+ “消费完成:” + bg);
}
}
}
public class BlockingQueueTest {
public static void main(String[] args) {
BlockingQueue bq = new ArrayBlockingQueue(1);
new Producer(bq).start();
new Consumer(bq).start();
}
}
4、CopyOnWrite系列的集合类
牛人真多
想问一下,volatile还是没办法解决例如 a++这样多线程调用吧?
volatile只能保证可见性,不能保证这种复合操作的原子性。
这个答得确实好~