juc详细知识

转自:http://blog.sina.com.cn/s/blog_777f59990102xofg.html

JUC是Java5.0开始提供的一组专门实现多线程并发处理的开发架构,利用juc开发架构可以有效的解决实际线程项目开发之中出现的死锁、阻塞、资源访问与公平机制。

本课程将为读者完整的讲解java.util.concurrent开发包之中的各个核心组成类的使用、操作原理分析,并且通过具体的实际代码对多线程开发的实际环境进行原理分析。 

 

面试题如下:

一、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

1.乐观锁,每次操作时不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止

2.悲观锁是会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

3.乐观锁可以使用volatile+CAS原语实现,带参数版本来避免ABA问题,在读取和替换的时候进行判定版本是否一致

4.悲观锁可以使用synchronize的以及Lock

 

二、什么是可重入锁(ReentrantLock)?

         重入锁指的是在某一个线程中可以多次获得同一把锁,在线程中多次操作有锁的方法

 

三、reentrantlock的优点和缺点

reentrantlock的优点

1.可以添加多个检控条件, 如果使用synchronized,则只能使用一个. 使用 reentrant locks 可以有多个wait()/notify() 队列. [译注:直接多new 几个ReentrantLock就可以了,不同的场景/条件用不同的ReentrantLock ]

2.可以控制线程得到锁的顺序,也就是有公平锁(按照进入顺序得到资源),也可以不按照顺就像.synchronized一样.

3.可以查看锁的状态, 锁是否被锁上了.

4.可以查看当前有多少线程再等待锁.

reentrantlock的缺点

1.需要使用import 引入相关的Class

2.不能忘记在finally 模块释放锁,这个看起来比synchronized 丑陋

3.synchronized可以放在方法的定义里面, 而reentrantlock只能放在块里面. 比较起来, synchronized可以减少嵌套

 

四、我们面对ReentrantLock和synchronized改如何选择?

1.Synchronized相比Lock,为许多开发人员所熟悉,并且简洁紧凑,如果现有程序已经使用了内置锁,那么尽量保持代码风格统一,尽量不引入Lock,避免两种机制混用,容易令人困惑,也容易发生错误。

 

2.在Synchronized无法满足需求的情况下,Lock可以作为一种高级工具,这些功能包括“可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁”否则还是优先使用Synchronized。

 

3.最后,未来更可能提升Synchronized而不是Lock的性能,因为Synchronized是JVM的内置属性,他能执行一些优化,例如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步,而如果基于类库的锁来实现这些功能,则可能性不大。

 

五、synchronized和java.util.concurrent.locks.Lock的异同?

1.Lock 和synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。

   2.一个 Lock 对象和一个synchronized 代码块之间的主要不同点是:

synchronized 代码块不能够保证进入访问等待的线程的先后顺序。

你不能够传递任何参数给一个 synchronized 代码块的入口。因此,对于 synchronized 代码块的访问等待设置超时时间是不可能的事情。

synchronized 块必须被完整地包含在单个方法里。而一个Lock 对象可以把它的 lock() 和 unlock() 方法的调用放在不同的方法里。

 

六、SynchronizedMap和ConcurrentHashMap(效率高)有什么区别?

1.java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。比起synchronizedMap来,它提供了好得多的并发性。多个读操作几乎总可以并发地执行,同时进行的读和写操作通常也能并发地执行,而同时进行的写操作仍然可以不时地并发进行(相关的类也提供了类似的多个读线程的并发性,但是,只允许有一个活动的写线程)。Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。前面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。

      2.在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。

 ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。另外一个不同点是,在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛ConcurrentModificationException。尽管 Iterator 的设计不是为多个线程的同时使用。

 

七、CopyOnWriteArrayList可以用于什么应用场景?

 多读少写的场景。读写并不是在同一个对象上。在写时会大面积复制数组,所以写的性能差,在写完成后将读的引用改为执行写的对象。CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException。在CopyOnWriteArrayList中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。

 

八、.如何让一段程序并发的执行,并最终汇总结果?

使用CyclicBarrier 和CountDownLatch都可以,使用CyclicBarrier 在多个关口处将多个线程执行结果汇总,CountDownLatch 在各线程执行完毕后向总线程汇报结果。

 

九、如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取最好的吞吐量?

1.配置线程池时CPU密集型任务可以少配置线程数,大概和机器的cpu核数相当,可以使得每个线程都在执行任务

        2.IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

        3.有界队列和无界队列的配置需区分业务场景,一般情况下配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列。

        4.任务非常多时,使用非阻塞队列使用CAS操作替代锁可以获得好的吞吐量。

 

十、同步有几种实现方法?

同步的实现方法有五种:1.同步方法;2.同步代码块;3.使用特殊域变量(volatile)实现线程同步;4.使用重入锁实现线程同步;5.使用局部变量实现线程同步 。

    同步的实现方面有两种,分别是synchronized,wait与notify wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

 

十一、volatile有什么用?能否用一句话说明下volatile的应用场景?

Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。

          您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

                          A. 对变量的写操作不依赖于当前值。

                          B. 该变量没有包含在具有其他变量的不变式中。

首先总结一下Volatile的特性:可见性,但不互斥.怎么理解这句话,首先可见性的原因是以为,这个关键字可以让变量不缓存在寄存器里面,每次取值都是直接从主存里面获取,所以每次都是最新的值.但是不互斥是因为没有锁,这里有个改变值的流程(读取-修改-写入),这是一个比读更耗时的一个操作,在没有加锁的情况下别的线程读取这个值可能是任何一个时刻的值;所以根据这个特性可以推导出使用Volatile在少写多读的情况下,性能非常好,当然首先要保证不会是多线程同时写.

 

Volatile有五个使用场景

 

1.作为状态标志

 

2.一次性安全发布

 

3.独立观察

 

4.volatile bean模式

 

5.     开销较低的读写锁策略

 

十二、什么场景下可以使用volatile替换synchronized?

只需要保证共享资源的可见性的时候可以使用volatile替代,synchronized保证可操作的原子性一致性和可见性。 volatile适用于新值不依赖于就值的情形

 

 

十三、线程池的启动策略?

1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也

不会马上执行它们。

2、当调用execute() 方法添加一个任务时,线程池会做如下判断:

   a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;

   b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。

   c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这

个任务;

   d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告

诉调用者“我不能再接受任务了”。

3、当一个线程完成任务时,它会从队列中取下一个任务来执行。

4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于

corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

 

 

十四、Java线程池中submit() 和 execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

 

 

十五、thread类中的yield方法有什么作用?

Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

 

 

十六、如何避免死锁

在有些情况下死锁是可以避免的。两种用于避免死锁的技术:

1)加锁顺序(线程按照一定的顺序加锁)

2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

 

十七、Synchronized有哪些缺点?

1.只有一个condition与锁相关联,这个condition是什么?就是synchronized对针对的对象锁。

2.synchronized无法中断一个正在等待获得锁的线程,也即多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。

 

十八、什么是Deamo线程,它有什么意义。

有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。Daemon的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。

 

十九、描述线程池分类,

分类:

1.         创建无大小限制的线程池(newCachedThreadPool())

2.         创建固定大小的线程池(newFixedThreadPool(int nThreads))

3.         单线程池 (newSingleThreadScheduledExecutor())

4.   创建定时调度池 (newScheduledThreadPool(int corePoolSize)

 

二十、线程池有几种拒绝策略,分别是哪些?

四种:

1.         ThreadPoolExecutor.AbortPolic(默认):当任务添加到线程池之中被拒绝的时候,这个时候会抛出RejectedExecutionException异常

2.         ThereeadPoolExecutor.CallerRunsPolicy:当任务被拒绝的时候,会在线程池当前正在执行线程的WORKER里面处此线程

3.         ThreadPoolExecutor.DiscardOldestPoliy:当被拒绝的时候,线程池会放弃队列之中等待最长时间的任务,并且将被拒绝的任务添加到队列之中

4.         ThreadPoolExecutor.DiscardPolicy:当任务添加拒绝的时候,将直接丢弃此线程

    原文作者:JUC
    原文地址: https://blog.csdn.net/qq_30675777/article/details/82751161
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞