java并发面试题(二)实战

本文列出了在工作中会用到的并发编程的实战问题,大家可以一起交流下,在回复中给出答案。

并发容器和框架

  1. 如何让一段程序并发的执行,并最终汇总结果?
  2. 如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取最好的吞吐量?
  3. 如何使用阻塞队列实现一个生产者和消费者模型?请写代码。
  4. 多读少写的场景应该使用哪个并发容器,为什么使用它?比如你做了一个搜索引擎,搜索引擎每次搜索前需要判断搜索关键词是否在黑名单里,黑名单每天更新一次。

Java中的锁

  1. 如何实现乐观锁(CAS)?如何避免ABA问题?
  2. 读写锁可以用于什么应用场景?
  3. 什么时候应该使用可重入锁?
  4. 什么场景下可以使用volatile替换synchronized?

并发工具

  1. 如何实现一个流控程序,用于控制请求的调用次数?

答案

可以阅读以下参考资料,知道答案后可以在回复中交流

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: java并发面试题(二)实战

  • Trackback 关闭
  • 评论 (29)
    • Jack
    • 2013/03/11 9:47上午

    有些题目还真不会哟

    • 宅男小何
    • 2013/03/11 3:26下午

    问题1:
    a.切分问题。
    b.每个线程执行的结果存入一个容器。
    c.汇总结果,做进一步处理。
    ================================
    很多问题不太清楚,没实战过。悲催~~~
    持续关注!

    • 看看FutureTask

        • 宅男小何
        • 2013/03/18 9:57上午

        Future这个只是一个线程池吧,特殊点是它可以获取到任务的一个返回结果。

        • ddl19901201
        • 2014/04/09 2:36下午

        使用countDownLatch

    • Fork/Join值得关注

    • 小志志
    • 2013/03/20 4:17下午

    1. 使用CyclicBarrier 和CountDownLatch都可以
    使用CyclicBarrier 在多个关口处将多个线程执行结果汇总
    CountDownLatch 在各线程执行完毕后向总线程汇报结果
    2. 配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务
    IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
    有界队列和无界队列的配置需区分业务场景,一般情况下配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列。
    任务非常多时,使用非阻塞队列使用cas操作替代锁可以获得好的吞吐量。
    4. CopyOnWriteArrayList这个容器适用于多读少写…读写并不是在同一个对象上。在写时会大面积复制数组,所以写的性能差,在写完成后将读的引用改为执行写的对象

    1. 读取内存值
    比较内存值和期望值
    替换内存值为要替换值

    带参数版本来避免aba问题,在读取和替换的时候进行判定版本是否一致

    2. 多读少写,读写锁支持多个读操作并发执行,写操作只能由一个线程来操作
    3. 重入锁指的是在某一个线程中可以多次获得同一把锁,在线程中多次操作有锁的方法。
    4. 只需要保证共享资源的可见性的时候可以使用volatile替代,synchronized保证可操作的原子性一致性和可见性。
    volatile适用于新值不依赖于就值的情形。

    • 回答的非常好。

    • 第一问用ForkJoinPool的子类RecursiveTask应该就可以吧???

    • tsingxu
    • 2013/03/20 5:15下午

    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的线程池

    • 公泰
    • 2013/03/20 9:12下午

    一天一次的更新需要加锁吗?new新对象出来替换掉老的就好

    • 公泰
    • 2013/03/20 9:13下午

    一天一次的更新需要加锁吗?new新对象出来替换掉老的就好。

    • txbhcml
    • 2013/10/09 11:04下午

    1.CyclicBarrier CountDownLatch join
    2. 配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务
    IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数 有界队列好,无界队列不可控,有可能导致内存不够 synchronousQueue吞吐率最高
    3 BlockingQueue put take
    4CopyOnWriteArrayList这个容器适用于多读少写 读的时候可以不用加锁
    1 compareAndSet AtomicStampedReference
    2多读少写
    3可轮询,可中断,定时,非块,公平队列等高级特性时候使用可重入锁
    4 单线程修改变量或不依赖当前值,且不与其他变量构成不变性条件时候使用volatile

    • 匿名
    • 2013/12/29 11:15上午

    什么场景下可以使用volatile替换synchronized

    volatile的应用场景一句话概括下来就是,对于共享资源,要做到读不加锁,写加锁,那么就必须使用volatile,这里的写加锁可以使用互斥锁,也可以使用CAS的乐观锁机制

    • 匿名
    • 2013/12/29 11:19上午

    如何让一段程序并发的执行,并最终汇总结果?
    1. 可以使用Callable+FutureTask+Executors+Future操作,让收集结果的线程阻塞等待与get()方法等待结果
    2. 可以使用Thread的join方法来阻塞等待结果
    3. 可以使用CountDownLatch来让收集结果的线程等待
    4. 可以使用CyclicBarrier来收让收集结果的线程等待,
    CountDownLatch是CyclicBarrier的一种,所以CountDownLatch能解决的问题,CyclicBarrier一定可以

    • 匿名
    • 2013/12/29 11:23上午

    如何使用阻塞队列实现一个生产者和消费者模型?请写代码

    使用基于数组的阻塞队列,有限次取水果和放水果

    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();

    }
    }

    • 匿名
    • 2013/12/29 11:32上午

    如何实现乐观锁(CAS)?如何避免ABA问题?

    CAS原语有三个值,一个是内存值,一个是期望值,一个是写入值。 在不加锁的情况下写入时,每次读取内存值,然后跟预期值比对,如果比对失败,反复的读和比对,直到成功。在CAS原语是一个原子操作,如果写入时,内存值发生改变,则写入值失败

    ABA问题可以使用AtomicStampedReference在做CAS操作时,一方面比较内存中的操作数与预期值是否一样,同时比较内存中的操作数的时间戳(或者修改次数)是否与预期值一样。如果本次修改操作成功,一定要修改操作数的时间戳,可以通过每次加1的方式。

    • 匿名
    • 2013/12/29 11:35上午

    读写锁可以用于什么应用场景?
    读写锁适用于读操作>>写操作,同时允许多个线程同时读,读读允许,读写互斥,写写互斥。这样的场景比较常见,比如,一个文件,我们可以要求同时读,但是不能读写或者写写同时发生。缓存Cache也可以实现同时读,互斥写。对于lucene这样的文本检索系统,也可以使用读写锁实现更新索引文件上写锁,读取索引加读锁。

    • 匿名
    • 2013/12/29 11:43上午

    什么时候应该使用可重入锁?

    1.公平锁
    2.多路条件,比如我要实现5个线程的同步,A->B->C->D->E->A,可以使用一个Lock,5个Condition来控制5个线程的步调
    3.实现轮询,中断的功能,只听说过,没用过。

      • 无为
      • 2014/11/20 9:11下午

      这个没太明白

    • 匿名
    • 2013/12/29 11:48上午

    如何实现一个流控程序,用于控制请求的调用次数?

    应该可以考虑使用Semaphore来控制, Semaphore本身是用来限制有限资源的争用。比如我要限制数据数据库连接池的连接数不超过20个,我可以在连接池的配置里面设置最大值,也可以在连接池的访问层来控制,这样可以减轻连接池的负担,也可以控制得不到连接的线程的行为,比如等待而不是扔个SQLException出来

  1. 赞楼上,都回答了啊。

      • 匿名
      • 2013/12/30 11:28上午

      谢谢博主,呵呵。最近在研究juc,看到这个好网站,所以就回答下,看看自己掌握多少。以后还等多学多用,如果上面回答的有什么问题,还望不吝批评指正啊。

  2. 我也来答一下,哈哈

    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系列的集合类

  3. 牛人真多

  4. 想问一下,volatile还是没办法解决例如 a++这样多线程调用吧?

      • 冬瓜大仙
      • 2014/05/29 10:50上午

      volatile只能保证可见性,不能保证这种复合操作的原子性。

    • 忍释
    • 2015/01/12 9:56下午

    方 腾飞 :
    回答的非常好。

    这个答得确实好~

return top