暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

每日脑补并发面试题(二)

胡金水 2020-04-16
268

点击上方“胡金水”,选择“置顶公众号”

技术文章第一时间送达!

作者:乌枭   来源:https://dwz.cn/Nrxag9gE

1、为什么使用Executor框架?


  • 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。

  • 调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源。

  • 接使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。


2、在Java中Executor和Executors的区别?


        Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。

 

        Executor 接口对象能执行我们的线程任务。 


        ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。

 

        使用ThreadPoolExecutor 可以创建自定义线程池。

 

        Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用get()方法获取计算的结果。

3、什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?


        原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。 

处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。 


        在Java中可以通过锁和循环CAS的方式来实现原子操作。CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作。

        原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。 


        int++并不是一个原子操作,所以当一个线程读取它的值并加1时,另外一个线程有可能会读到之前的值,这就会引发错误。

 

为了解决这个问题,必须保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的原子包装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。

        在java.util.concurrent这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。

原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference 


原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray 


原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater 


解决ABA问题的原子类:AtomicMarkableReference(通过引入一个boolean来反映中间有没有变过)AtomicStampedReference(通过引入一个int来累加来反映中间有没有变过)


4、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?


Lock接口比同步方法和同步块提供了更具扩展性的锁操作。

 

他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。


它的优势有:

  • 可以使锁更公平

  • 可以使线程在等待锁的时候响应中断

  • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间

  • 可以在不同的范围,以不同的顺序获取和释放锁


整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

5、什么是Executors框架?


Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。


无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池。

6、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?


阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。


这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。


阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。


JDK7提供了7个阻塞队列。分别是: 

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。 

  • LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。 

  • PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。 

  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。 

  • SynchronousQueue:一个不存储元素的阻塞队列。 

  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。 

  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。


Java 5之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实现生产者,消费者模式,主要的技术就是用好,wait ,notify,notifyAll,sychronized这些关键字。而在java 5之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面也有保障。

 

BlockingQueue接口是Queue的子接口,它的主要用途并不是作为容器,而是作为线程同步的的工具,因此他具有一个很明显的特性,当生产者线程试图向BlockingQueue放入元素时,如果队列已满,则线程被阻塞,当消费者线程试图从中取出一个元素时,如果队列为空,则该线程会被阻塞,正是因为它所具有这个特性,所以在程序中多个线程交替向BlockingQueue中放入元素,取出元素,它可以很好的控制线程之间的通信。


阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。

如果觉得文章不错,可以扫描下方二维码进行关注。

文章转载自胡金水,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论