java 2018面试题-多线程汇总(含解答)


        学习,内容越多、越杂的知识,越须要进行深入的总结,这样才能记忆深入,将知识变成本身的。这篇文章主要是对多线程的问题进行总结的,所以罗列了本身整理的多线程的问题,都是本身以为比较经典和一些大企业面试会问到的。这些多线程的问题,有些来源于各大网站、有些来源于本身的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,java

1多线程的几种实现方式,什么是线程安全。

Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口经过FutureTask包装器来建立Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。程序员

其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。面试

 ==================================================================编程

volatile的原理,做用,能代替锁么。

volatile 关键字的做用 保证内存的可见性 防止指令重排数组

注意:volatile 并不保证原子性缓存

可见性原理安全

volatile 保证可见性的原理是在每次访问变量时都会进行一次刷新,所以每次访问都是主内存中最新的版本。因此 volatile 关键字的做用之一就是保证变量修改的实时可见性。服务器

一个很是重要的问题,是每一个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的做用的前提是要理解Java内存模型,这里就不讲Java内存模型了,能够参见第31点,volatile关键字的做用主要有两个:数据结构

(1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,必定是最新的数据多线程

(2)代码底层执行不像咱们看到的高级语言----Java程序这么简单,它的执行是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,固然这也必定程度上下降了代码执行效率

从实践角度而言,volatile的一个重要做用就是和CAS结合,保证了原子性,详细的能够参见java.util.concurrent.atomic包下的类,好比AtomicInteger。

 

volatile 和 synchronized区别

一、 volatile 轻量级,只能修饰变量。synchronized重量级,还可修饰方法

二、volatile 只能保证数据的可见性,不能用来同步,由于多个线程并发访问 volatile 修饰的变量不会 阻塞。

synchronized 不只保证可见性,并且还保证原子性,由于,只有得到了锁的线程才能进入临界区,从而保证临界区中的全部语句都所有执行。多个线程争抢 synchronized 锁对象时,会出现阻塞。

   volatile 并不能保证线程安全性。而 synchronized 则可实现线程的安全性

 ==================================================================

3.sleep和wait的区别。

对于sleep()方法,咱们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

sleep()方法致使了程序暂停执行指定的时间,让出cpu该其余线程,可是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程当中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

获取对象锁进入运行状态。

这个问题常问,sleep方法和wait方法均可以用来放弃CPU必定的时间,不一样点在于若是线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器

 ==================================================================

sleep和sleep(0)的区别。

Sleep 接口均带有表示睡眠时间长度的参数 timeout。调用以上提到的 Sleep 接口,会有条件地将调用线程从当前处理器上移除,而且有可能将它从线程调度器的可运行队列中移除。这个条件取决于调用 Sleep 时timeout 参数。

当 timeout = 0, 即 Sleep(0),若是线程调度器的可运行队列中有大于或等于当前线程优先级的就绪线程存在,操做系统会将当前线程从处理器上移除,调度其余优先级高的就绪线程运行;若是可运行队列中的没有就绪线程或全部就绪线程的优先级均低于当前线程优先级,那么当前线程会继续执行,就像没有调用 Sleep(0)同样。

当 timeout > 0 时,如:Sleep(1),会引起线程上下文切换:调用线程会从线程调度器的可运行队列中被移除一段时间,这个时间段约等于 timeout 所指定的时间长度。为何说约等于呢?是由于睡眠时间单位为毫秒,这与系统的时间精度有关。一般状况下,系统的时间精度为 10 ms,那么指定任意少于 10 ms但大于 0 ms 的睡眠时间,均会向上求值为 10 ms。

而调用 SwitchToThread() 方法,若是当前有其余就绪线程在线程调度器的可运行队列中,始终会让出一个时间切片给这些就绪线程,而无论就绪线程的优先级的高低与否

 ==================================================================

Lock与Synchronized的区别 。

二者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

2.synchronized没法判断是否获取锁的状态,Lock能够判断是否获取到锁;

3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程当中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),不然容易形成线程死锁;

4.用synchronized关键字的两个线程1和线程2,若是当前线程1得到锁,线程2线程等待。若是线程1阻塞,线程2则会一直等待下去,而Lock锁就不必定会等待下去,若是尝试获取不到锁,线程能够不用一直等待就结束了;

5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(二者皆可)

6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少许的同步问题。

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为何会出现Lock呢?

  若是一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其余线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种状况:

1)获取锁的线程执行完了该代码块,而后线程释放对锁的占有;

2)线程执行发生异常,此时JVM会让线程自动释放锁。

  那么若是这个获取锁的线程因为要等待IO或者其余缘由(好比调用sleep方法)被阻塞了,可是又没有释放锁,其余线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

  所以就须要有一种机制能够不让等待的线程一直无期限地等待下去(好比只等待必定的时间或者可以响应中断),经过Lock就能够办到。

  再举个例子:当有多个线程读写文件时,读操做和写操做会发生冲突现象,写操做和写操做会发生冲突现象,可是读操做和读操做不会发生冲突现象。

  可是采用synchronized关键字来实现同步的话,就会致使一个问题:

  若是多个线程都只是进行读操做,因此当一个线程在进行读操做时,其余线程只能等待没法进行读操做。

  所以就须要一种机制来使得多个线程都只是进行读操做时,线程之间不会发生冲突,经过Lock就能够办到。

  另外,经过Lock能够知道线程有没有成功获取到锁。这个是synchronized没法办到的。

  总结一下,也就是说Lock提供了比synchronized更多的功能。可是要注意如下几点:

1)Lock不是Java语言内置的,synchronized是Java语言的关键字,所以是内置特性。Lock是一个类,经过这个类能够实现同步访问;

2)Lock和synchronized有一点很是大的不一样,采用synchronized不须要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完以后,系统会自动让线程释放对锁的占用;而Lock则必需要用户去手动释放锁,若是没有主动释放锁,就有可能致使出现死锁现象。

 

 ==================================================================

6 synchronized的原理是什么,通常用在什么地方(好比加在静态方法和非静态方法的区别,静态方法和非静态方法同时执行的时候会有影响吗),解释如下名词:重排序,自旋锁,偏向锁,轻量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁。

使用独占锁机制来解决,是一种悲观的并发策略,抱着一副“总有刁民想害朕”的态势,每次操做数据的时候都认为别的线程会参与竞争修改,因此直接加锁。同一刻只能有一个线程持有锁,那其余线程就会阻塞。线程的挂起恢复会带来很大的性能开销,尽管jvm对于非竞争性的锁的获取和释放作了不少优化,可是一旦有多个线程竞争锁,频繁的阻塞唤醒,仍是会有很大的性能开销的。因此,使用synchronized或其余重量级锁来处理显然不够合

乐观的解决方案,顾名思义,就是很大度乐观,每次操做数据的时候,都认为别的线程不会参与竞争修改,也不加锁。若是操做成功了那最好;若是失败了,好比中途确有别的线程进入并修改了数据(依赖于冲突检测),也不会阻塞,能够采起一些补偿机制,通常的策略就是反复重试。很显然,这种思想相比简单粗暴利用锁来保证同步要合理的多。

 ==================================================================

  1. 线程安全的map有哪些,concurrenthashmap是如何实现线程安全的(jdk1.8大不一样)?

HashTable   ConcurrentHashMap 

链表改为了红黑树,当链表中的结点达到一个阀值TREEIFY_THRESHOLD时,会将链表转换为红黑树,查询效率提从原来的O(n),提升为O(logn)

将每一个segment的分段锁ReentrantLock改成CAS+Synchronized

并发环境下为何使用ConcurrentHashMap

1. HashMap在高并发的环境下,执行put操做会致使HashMap的Entry链表造成环形数据结构,从而致使Entry的next节点始终不为空,所以产生死循环获取Entry

2. HashTable虽然是线程安全的,可是效率低下,当一个线程访问HashTable的同步方法时,其余线程若是也访问HashTable的同步方法,那么会进入阻塞或者轮训状态。

3. 在jdk1.6中ConcurrentHashMap使用锁分段技术提升并发访问效率。首先将数据分红一段一段地存储,而后给每一段数据配一个锁,当一个线程占用锁访问其中一段数据时,其余段的数据也能被其余线程访问。然而在jdk1.8中的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全,底层依然采用数组+链表+红黑树的存储结构。 

 ==================================================================

锁有哪几种?

  •  公平锁/非公平锁
  • 可重入锁
  • 独享锁/共享锁
  • 互斥锁/读写锁
  • 乐观锁/悲观锁
  • 分段锁
  • 偏向锁/轻量级锁/重量级锁
  • 自旋锁
  1. 公平锁和非公平锁。

公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁。

非公平所:非公平锁是指多个线程获取锁的顺序并非按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会形成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,经过构造函数指定该锁是不是公平锁,默认是非公平锁。非公平锁的优势在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。因为其并不像ReentrantLock是经过AQS的来实现线程调度,因此并无任何办法使其变成公平锁。//默认是不公平锁,传入true为公平锁,不然为非公平锁

ReentrantLock reentrantLock =  new ReetrantLock();

共享锁和独享锁

独享锁:一次只能被一个线程所访问

共享锁:线程能够被多个线程所持有。

ReadWriteLock 读锁是共享锁,写锁是独享锁。

对于Java ReentrantLock而言,其是独享锁。可是对于Lock的另外一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是很是高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是经过AQS来实现的,经过实现不一样的方法,来实现独享或者共享。
对于Synchronized而言,固然是独享锁。

乐观锁和悲观锁。 

乐观锁:对于一个数据的操做并发,是不会发生修改的。在更新数据的时候,会尝试采用更新,不断重入的方式,更新数据。

悲观锁:对于同一个数据的并发操做,是必定会发生修改的。所以对于同一个数据的并发操做,悲观锁采用加锁的形式。悲观锁认为,不加锁的操做必定会出问题,

分段锁

1.7及以前的concurrenthashmap。并发操做就是分段锁,其思想就是让锁的粒度变小。

分段锁实际上是一种锁的设计,并非具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是经过分段锁的形式来实现高效的并发操做。
咱们以ConcurrentHashMap来讲一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即相似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每一个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当须要put元素的时候,并非对整个hashmap进行加锁,而是先经过hashcode来知道他要放在那一个分段中,而后对这个分段进行加锁,因此当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
可是,在统计size的时候,可就是获取hashmap全局信息的时候,就须要获取全部的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操做不须要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操做。

偏向锁/轻量级锁/重量级锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。下降获取锁的代价 这三种锁是指锁的状态,而且是针对Synchronized。在Java 5经过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是经过对象监视器在对象头中的字段来代表的。
  偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。下降获取锁的代价。
   轻量级锁 是指当锁是偏向锁的时候,被另外一个线程所访问,偏向锁就会升级为轻量级锁,其余线程会经过自旋的形式尝试获取锁,不会阻塞,提升性能。
重量级锁是指当锁为轻量级锁的时候,另外一个线程虽然是自旋,但自旋不会一直持续下去,当自旋必定次数的时候,尚未获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其余申请的线程进入阻塞,性能下降。

6自旋锁

在Java中,自旋锁是指尝试获取锁的线程不会当即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减小线程上下文切换的消耗,缺点是循环会消耗CPU。

不少synchronized里面的代码只是一些很简单的代码,执行时间很是快,此时等待的线程都加锁多是一种不太值得的操做,由于线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得很是快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界作忙循环,这就是自旋。若是作了屡次忙循环发现尚未得到锁,再阻塞,这样多是一种更好的策略。

 

7可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
对于Java ReentrantLock而言, 他的名字就能够看出是一个可重入锁,其名字是Re entrant Lock从新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可必定程度避免死锁。

synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

}

synchronized void setB() throws Exception{

    Thread.sleep(1000);

}

上面的代码就是一个可重入锁的一个特色,若是不是可重入锁的话,setB可能不会被当前线程执行,可能形成死锁。

 ==================================================================

公平锁,读写锁等如何实现?

 ==================================================================

10 synchronize能加在哪些地方?什么区别?

  1. synchronized 在方法上,全部这个类的加了 synchronized 的方法,在执行时,会得到一个该类的惟一的同步锁,当这个锁被占用时,其余的加了 synchronized 的方法就必须等待
     2.加在对象上的话,就是以这个对象为锁,其余也以这个对象为锁的代码段,在这个锁被占用时,就必须等待

==================================================================

11 原子数据对象的原理?

以AtomicInteger为例 AtomicInteger是对int类型的一个封装,提供原子性的访问和更新操做,其原子性操做的实现是基于CAS(compare-and-swap)技术。

所谓CAS,表现为一组指令,当利用CAS执行试图进行一些更新操做时。会首先比较当前数值,若是数值未变,表明没有其它线程进行并发修改,则成功更新。若是数值改变,则可能出现不一样的选择,要么进行重试,要么就返回是否成功。也就是所谓的“乐观锁”。

从AtomicInteger的内部属性能够看出,它依赖于Unsafe提供的一些底层能力,进行底层操做;以volatile的value字段,记录数值,以保证可见性。

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();

private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

private volatile int value;

具体的原子操做细节,能够参考任意一个原子更新方法,好比下面的getAndIncrement。Unsafe会利用value字段的内存地址偏移,直接完成操做。

public final int getAndIncrement() {

  return U.getAndAddInt(this, VALUE, 1);

}

由于getAndIncrement须要返回数值,因此须要添加失败重试逻辑。

public final int getAndAddInt(Object o, long offset, int delta) {

  int v;

  do {

  v = getIntVolatile(o, offset);

  } while (!weakCompareAndSetInt(o, offset, v, v + delta));

  return v;

}

而相似compareAndSet这种返回boolean类型的函数,由于其返回值表现的就是是否成功与否,因此不须要重试。

public final boolean compareAndSet(int expectedValue, int newValue)

CAS是Java并发中所谓lock-free机制的基础。

==================================================================

12 reentrantlock相关知识,condition如何使用?(很重要的知识点,强烈推荐阅读ArrayBlockingQueue源码,教科书般)

ReentrantLock

ReentrantLock能够等同于synchronized使用。是一个可重入的互斥锁,它具备与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock 类实现了Lock ,它拥有与 synchronized 相同的并发性和内存语义,可是添加了相似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用状况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 能够花更少的时候来调度线程,把更多时间用在执行线程上。

Condition

线程之间的通讯。Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成大相径庭的对象,以便经过将这些对象与任意 Lock 实现组合使用。

==================================================================

13 CyclicBarrier和CountDownLatch的区别

两个看上去有点像的类,都在java.util.concurrent下,均可以用来表示代码运行到某个点上,两者的区别在于:

(1)CyclicBarrier的某个线程运行到某个点上以后,该线程即中止运行,直到全部的线程都到达了这个点,全部线程才从新运行;CountDownLatch则不是,某线程运行到某个点上以后,只是给某个数值-1而已,该线程继续运行

(2)CyclicBarrier只能唤起一个任务,CountDownLatch能够唤起多个任务

(3)CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了

==================================================================

14 volatile的相关知识(内存屏障,重排)

==================================================================

15 ThreadLocal原理和使用?(超级有用的知识点,工做中使用不少,让代码漂亮不少)

简单说ThreadLocal就是一种以空间换时间的作法,在每一个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,天然就没有线程安全方面的问题了

==================================================================

1六、为何要使用线程池

避免频繁地建立和销毁线程,达到线程对象的重用。另外,使用线程池还能够根据项目灵活地控制并发的数目。

==================================================================

15 多个线程同步等待?(CountDownLatch,CyclicBarrier,Semaphore信号量不少语言都有,实际上使用不是不少,线程池就能够实现大部分等待功能)

==================================================================

16线程池?(种类,重要的方法,这个通常是使用层面,简单)

==================================================================

17、Java中如何获取到线程dump文件

死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:

(1)获取到线程的pid,能够经过使用jps命令,在Linux环境下还可使用ps -ef | grep java

(2)打印线程堆栈,能够经过使用jstack pid命令,在Linux环境下还可使用kill -3 pid

另外提一点,Thread类提供了一个getStackTrace()方法也能够用于获取线程堆栈。这是一个实例方法,所以此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈

==================================================================

18、一个线程若是出现了运行时异常会怎么样

若是这个异常没有被捕获的话,这个线程就中止执行了。另外重要的一点是:若是这个线程持有某个某个对象的监视器,那么这个对象监视器会被当即释放

==================================================================

19、如何在两个线程之间共享数据(线程同步)

经过在线程之间共享对象就能够了,而后经过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的

==================================================================

20、ReadWriteLock是什么

首先明确一下,不是说ReentrantLock很差,只是ReentrantLock某些时候有局限。若是使用ReentrantLock,可能自己是为了防止线程A在写数据、线程B在读数据形成的数据不一致,但这样,若是线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,可是仍是加锁了,下降了程序的性能。

由于这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提高了读写的性能。

==================================================================

2一、FutureTask是什么

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面能够传入一个Callable的具体实现类,能够对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操做。固然,因为FutureTask也是Runnable接口的实现类,因此FutureTask也能够放入线程池中。

==================================================================

2二、Linux环境下如何查找哪一个线程使用CPU最长

这是一个比较偏实践的问题,这种问题我以为挺有意义的。能够这么作:

(1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过

(2)top -H -p pid,顺序不能改变

这样就能够打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操做系统原生线程的线程号,我笔记本山没有部署Linux环境下的Java工程,所以没有办法截图演示,网友朋友们若是公司是使用Linux环境部署项目的话,能够尝试一下。

使用"top -H -p pid"+"jps pid"能够很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的缘由,通常是由于不当的代码操做致使了死循环。

最后提一点,"top -H -p pid"打出来的LWP是十进制的,"jps pid"打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。

 ==================================================================

23、怎么唤醒一个阻塞的线程

若是线程是由于调用了wait()、sleep()或者join()方法而致使的阻塞,能够中断线程,而且经过抛出InterruptedException来唤醒它;若是线程遇到了IO阻塞,无能为力,由于IO是操做系统实现的,Java代码并无办法直接接触到操做系统。

==================================================================

24、若是你提交任务时,线程池队列已满,这时会发生什么

这里区分一下:

1若是使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,不要紧,继续添加任务到阻塞队列中等待执行,由于LinkedBlockingQueue能够近乎认为是一个无穷大的队列,能够无限存听任务

2 若是使用的是有界队列好比ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增长线程数量,若是增长了线程数量仍是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy

==================================================================

26、什么是CAS什么是AQS

CAS,全称为Compare and Swap,即比较-替换。假设有三个操做数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改成B并返回true,不然什么都不作并返回false。固然CAS必定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,不然旧的预期值A对某条线程来讲,永远是一个不会变的值A,只要某次CAS操做失败,永远都不可能成功。

简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。

若是说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式链接全部的Entry,比方说ReentrantLock,全部等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。

AQS定义了对双向队列全部的操做,而只开放了tryLock和tryRelease方法给开发者使用,开发者能够根据本身的实现重写tryLock和tryRelease方法,以实现本身的并发功能。

==================================================================

27、单例模式的线程安全性

老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被建立一次出来。单例模式有不少种的写法,我总结一下:

(1)饿汉式单例模式的写法:线程安全

(2)懒汉式单例模式的写法:非线程安全

(3)双检锁单例模式的写法:线程安全

==================================================================

28、Semaphore有什么做用

Semaphore就是一个信号量,它的做用是限制某段代码块的并发数。Semaphore有一个构造函数,能够传入一个int型整数n,表示某段代码最多只有n个线程能够访问,若是超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此能够看出若是Semaphore构造函数中传入的int型整数n=1,至关于变成了一个synchronized了。

==================================================================

29、Hashtable的size()方法中明明只有一条语句"return count",为何还要作同步?

主要缘由有两点:

(1)同一时间只能有一条线程执行固定类的同步方法,可是对于类的非同步方法,能够多条线程同时访问。因此,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则能够正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,可是没有对size++,线程B就已经读取size了,那么对于线程B来讲读取到的size必定是不许确的。而给size()方法加了同步以后,意味着线程B调用size()方法只有在线程A调用put方法完毕以后才能够调用,这样就保证了线程安全性

(2)CPU执行代码,执行的不是Java代码,这点很关键,必定得记住。Java代码最终是被翻译成机器码执行的,机器码才是真正能够和硬件电路交互的代码。即便你看到Java代码只有一行,甚至你看到Java代码编译以后生成的字节码也只有一行,也不意味着对于底层来讲这句语句的操做只有一个。一句"return count"假设被翻译成了三句汇编语句执行,一句汇编语句和其机器码作对应,彻底可能执行完第一句,线程就切换了。

==================================================================

30、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,但愿每一个人都能看到而且思考一下,由于这个问题很是好、很是实际、很是专业。关于这个问题,我的见解是:

(1)高并发、任务执行时间短的业务,线程池线程数能够设置为CPU核数+1,减小线程上下文的切换

(2)并发不高、任务执行时间长的业务要区分开看:

a)假如是业务时间长集中在IO操做上,也就是IO密集型的任务,由于IO操做并不占用CPU,因此不要让全部的CPU闲下来,能够加大线程池中的线程数目,让CPU处理更多的业务

b)假如是业务时间长集中在计算操做上,也就是计算密集型任务,这个就没办法了,和(1)同样吧,线程池中的线程数设置得少一些,减小线程上下文的切换

(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于总体架构的设计,看看这些业务里面某些数据是否能作缓存是第一步,增长服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能须要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

==================================================================

31、怎么检测一个线程是否持有对象监视器

我也是在网上看到一道多线程面试题才知道有方法能够判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着"某条线程"指的是当前线程

==================================================================

32、ConcurrentHashMap的并发度是什么

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时能够有16条线程操做ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优点,任何状况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

=================================================================

相关文章
相关标签/搜索