01 什么是线程?
线程是操作系统能够进⾏运算调度的最⼩单位,它被包含在进程之中,是进程中的实际运作单位,可以使⽤多线程对进⾏运算提速。
02 什么是线程安全和线程不安全?
线程安全:
就是多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏访问,直到该线程读取完,其他线程才可使⽤。不会出现数据不⼀致或者数据污染。Vector 是⽤同步⽅法来实现线程安全的, ⽽和它相似的ArrayList不是线程安全的。
线程不安全:
就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考虑线程同步,否则的话就可能影响线程安全。
03 什么是自旋锁?
自旋锁是SMP架构中的⼀种low-level的同步机制。
当线程A想要获取⼀把自旋锁⽽该锁⼜被其它线程锁持有时,线程A会在⼀个循环中自旋以检测锁是不是已经可⽤了。
自旋锁需要注意:
- 由于自旋时不释放CPU,因⽽持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会⼀直在那⾥自旋,这就会浪费CPU时间。
- 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。
⽬前的JVM实现自旋会消耗CPU,如果⻓时间不调⽤doNotify⽅法,doWait⽅发会⼀直自旋,CPU会消耗太⼤。
自旋锁⽐较适⽤于锁使⽤者保持锁时间⽐较短的情况,这种情况自旋锁的效率⽐较⾼。
自旋锁是⼀种对多处理器相当有效的机制,⽽在单处理器⾮抢占式的系统中基本上没有做⽤。
04 什么是CAS?
- CAS(compare and swap)的缩写,中⽂翻译成⽐较并交换。
- CAS 不通过JVM,直接利⽤java本地⽅ JNI(Java Native Interface为JAVA本地调⽤),直接调⽤CPU 的cmpxchg(是汇编指令)指令。
- 利⽤CPU的CAS指令,同时借助JNI来完成Java的⾮阻塞算法,实现原⼦操作。其它原⼦操作都是利⽤类似的特性完成的。
- 整个java.util.concurrent都是建⽴在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很⼤的提升。
- CAS是项乐观锁技术,当多个线程尝试使⽤CAS同时更新同⼀个变量时,只有其中⼀个线程能更新变量的值,⽽其它线程都失败,失败的线程并不会被挂起,⽽是被告知这次竞争中失败,并可以再次尝试。
- 使⽤CAS在线程冲突严重时,会⼤幅降低程序性能;CAS只适合于线程冲突较少的情况使⽤。
- synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了⾼吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;⽽线程冲突严重的情况下,性能远⾼于CAS。
05 什么是乐观锁和悲观锁?
1.悲观锁
Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使⽤⼀致的锁定协议来协调对共享状态的访问,可以确保⽆论哪个线程持有共享变量的锁,都采⽤独占的⽅式来访问这些变量。独占锁其实就是⼀种悲观锁,所以可以说synchronized是悲观锁。
2.乐观锁
乐观锁( Optimistic Locking)其实是⼀种思想。相对悲观锁⽽⾔,乐观锁假设认为数据⼀般情况下不会造成冲突,所以在数据进⾏提交更新的时候,才会正式对数据的冲突与否进⾏检测,如果发现冲突了,则让返回⽤户错误的信息,让⽤户决定如何去做。memcached使⽤了cas乐观锁技术保证数据⼀致性。
06 什么是AQS?
1、AbstractQueuedSynchronizer简称AQS,是⼀个⽤于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的⼤量细节问题。
2、AQS使⽤⼀个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护⼀个等待状态waitStatus。
07 什么是原⼦操作?在Java Concurrency API中有哪些原⼦类(atomic classes)?
- 原⼦操作是指⼀个不受其他操作影响的操作任务单元。原⼦操作是在多线程环境下避免数据不⼀致必须的⼿段。
- int++并不是⼀个原⼦操作,所以当⼀个线程读取它的值并加1时,另外⼀个线程有可能会读到之前的值,这就会引发错误。
- 为了解决这个问题,必须保证增加操作是原⼦的,在JDK1.5之前我们可以使⽤同步技术来做到这⼀点。
到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的装类,它们可以自动的保证对于他们的操作是原⼦的并且不需要使⽤同步。
08 什么是Executors框架?
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建⼀个可缓存线程池,如果线程池⻓度超过处理需要,可灵活回收空闲线程,若⽆可回收,则新建线程。newFixedThreadPool 创建⼀个定⻓线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。newScheduledThreadPool 创建⼀个定⻓线程池,⽀持定时及周期性任务执⾏。newSingleThreadExecutor 创建⼀个单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执⾏。
09 什么是阻塞队列?如何使⽤阻塞队列来实现⽣产者-消费者模型?
1、JDK7提供了7个阻塞队列。(也属于并发容器)
ArrayBlockingQueue :⼀个由数组结构组成的有界阻塞队列。LinkedBlockingQueue :⼀个由链表结构组成的有界阻塞队列。PriorityBlockingQueue :⼀个⽀持优先级排序的⽆界阻塞队列。DelayQueue:⼀个使⽤优先级队列实现的⽆界阻塞队列。SynchronousQueue:⼀个不存储元素的阻塞队列。LinkedTransferQueue:⼀个由链表结构组成的⽆界阻塞队列。LinkedBlockingDeque:⼀个由链表结构组成的双向阻塞队列。
2、概念:阻塞队列是⼀个在队列基础上⼜⽀持了两个附加操作的队列。
3、2个附加操作:
3.1. ⽀持阻塞的插⼊⽅法:队列满时,队列会阻塞插⼊元素的线程,直到队列不满。
3.2 ⽀持阻塞的移除⽅法:队列空时,获取元素的线程会等待队列变为⾮空。
10 什么是Callable和Future?
1、Callable 和 Future 是⽐较有趣的⼀对组合。当我们需要获取线程的执⾏结果时,就需要⽤到它们。Callable⽤于产⽣结果,Future⽤于获取结果。
2、Callable接⼝使⽤泛型去定义它的返回类型。Executors类提供了⼀些有⽤的⽅法去在线程池中执⾏Callable内的任务。由于Callable任务是并⾏的,必须等待它返回的结果。java.util.concurrent.Future对象解决了这个问题。
3、在线程池提交Callable任务后返回了⼀个Future对象,使⽤它可以知道Callable任务的状态和得到Callable返回的执⾏结果。Future提供了get()⽅法,等待Callable结束并获取它的执⾏结果。