【学习笔记】Java核心技术——集合
1.什么是多任务?
2.什么是线程?
3.多进程与多线程的本质区别?
4.创建线程的方法(2种)
5.为何不要调用Thread类或Runnable对象的run方法?
6.线程中断的原因?
7.有无强制终止线程的方法?interrupt方法的用途?调用interrupt方法时,线程会发生什么变化?
8.调用sleep方法(或其它的可中断方法),isInterrupted检测有无用处?
9.在中断状态被置位时,调用sleep方法会发生什么变化?
10.interrupted和isInterrupted的区别?
11.java.lang.Thread()中包含哪些方法,对应的含义是什么?
12.线程的状态有哪6个?
13.如何获取当前线程的状态?
14.被阻塞和等待的区别是什么?
15.线程状态转换图?
16.线程的属性包括?
17.常见的线程优先级是?线程优先级高度依赖于什么?
18.守护线程的用途?当系统只剩下守护进程时,虚拟机会怎么样?
19.为什么需要未捕获异常?如何使用uncaughtException方法?
20.锁对象及其使用方式?使用锁对象是能否使用带资源的try?
21.当两个线程同时访问同一个对象时,锁提供服务的方式是?
22.条件对象的使用?
23.signalAll() 和 signal()的区别?
24.锁和条件的关键之处?
25.synchronized关键字的作用范围和区别?
26.内部锁和条件存在的局限?
27.在代码中应该使用Lock和Condition对象还是同步方法?
28.notifyAll()方法和notify()方法的区别?
29.监视器的特征?
30.使用同步出现错误的可能原因?
31.同步格言?
32.volatile关键字为实例域的同步访问提供了一种免锁机制。
33.为什么volatile变量不能提供原子性?
34.final变量能否提供安全性访问?
35.AtomicInteger类提供了方法incrementAndGet和decrementAndGet?
36.Atomic类有哪些方法?
37.为什么有大量线程要访问相同的原子值,性能会大幅下降?
38.为什么increment方法不会返回原值?
39.产生死锁的原因?
40.使用线程局部变量的原因?
41.使用tryLock方法的原因?
42.tryLock 工作原理
43.lock方法与tryLock方法的区别?
44.java.util.concurrent.locks包定义了哪两个锁类?
45.使用读/写锁的必要步骤?
46.为什么要弃用stop方法?
47.为什么要启用suspend方法?
48.stop方法和suspend方法的共同点?
49.什么时候会出现阻塞队列?
50.阻塞队列的方法及其含义?
51.阻塞队列方法分类依据?
52.java.util.concurrent包提供的阻塞队列变种有?
53.线程安全的集合?
54.集合返回弱一致性的迭代器意味着什么?
55.什么是并发散列映射提供的批操作?
56.并发散列映射批操作有哪些?
57.并发散列映射批操作的版本有?
58.Callable和Runnable的区别?
59.Future的作用?
60.FutureTask包装器的作用?
61.为什么需要使用线程池?
62.执行者工厂方法及作用?
63.如何将一个Runnable对象或Callable对象提交给ExecutorService?
64.使用连接线程池是应该做的事情有?
65.ScheduledExecutorService接口的目的是什么?
66.控制任务组?
67.Fork-Join框架的作用?
68.fork-join框架如何平衡可用线程的工作负载?
69.可完成Future的作用?
70.可完成Future的动作及其含义?
71.组合多个future的方法有?
72.同步器的类及其含义?
73.关于信号量
74.关于倒计时门栓?
75.关于障栅
76.关于交换器
面试常见问题整理:
AQS:
AQS原理概览?
AQS对资源的共享方式?
AQS介绍?
关于synchronized关键字:
说说自己对于synchronized关键字的了解?
说说自己是怎么使用synchronized关键字,在项目中用到了吗?
讲一下synchronized关键字的底层原理?
说说JDK1.6之后的synchronized关键字底层做了哪些优化?可以详细介绍一下这些优化吗?
谈谈synchronized和ReenTrantLock的区别?
线程池:
为什么要使用线程池?
实现Runnable和Callable接口的区别?
执行execute()方法和submit()方法的区别是什么?
如何创建线程池?
Atomic原子类:
介绍一下Atomic原子类?
JUC包中的原子类是哪4类?
讲讲AtomicInteger的使用?
能不能简单介绍一下AtomicInteger类的原理?
同一刻运行多个程序的能力
一个任务称为一个线程,它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序
每个进程拥有自己的一整套变量,而线程则共享数据。
单独的线程中执行一个任务的过程如下:
直接调用run方法,只会执行同一个线程中的任务,而不会启动新线程。调用Thread.start方法将创建一个执行run方法的新线程。
没有,interrupt方法可以用来请求终止线程。调用interrupt方法是,线程的中断状态将被置位。
无用
不会休眠,会清除这一状态,并抛出InterruptedException
interrupted是一个静态方法,它检测当前的线程是否被中断,调用interrupted方法会清除该线程的中断状态
isInterrupted是一个实例方法,用来检验是否有线程被中断,调用该方法不会改变线程的中断状态。
start:用来启动一个线程,调用start方法后,系统才会开启一个新的线程来执行用户定义的用户
run:通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务,必须重写run方法
sleep:交出CPU,让CPU去执行其他的任务,但不会释放锁,sleep(long millis) sleep(long millis,int nanoseconds),如果调用了sleep方法,必须不会InterruptedException异常或者将该异常向上层抛出,当线程睡眠时间满后,不一定会立即得到执行,因为此时CPU可能正在执行其他任务,调用sleep方法相当于让线程进入阻塞状态
yield:调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
wait:会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限
join:调用无参join方法,等待tjoin的hread执行完毕;调用指定了时间参数的join方法,等待一定的时间
interrupt:单独调用interrupt方法可以是处于阻塞状态的线程抛出一个InterruptedException,可以用来中断一个正处于阻塞状态的线程
不能中断处于正在运行中的线程
调用interrupt方法相当于将中断标志位置为true,调用isInterrupted()判断中断标志是否被置位来中断线程的执行
stop:废弃,调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有对象锁的话,会完全释放锁,导致对象状态不一致,所以stop方法基本不会被用到
destory:废弃
getId:得到线程的ID
getName和setName:获取或者设置线程名称
getPriority 和 setPriority:获取或设置线程优先级
setDaemon和isDaemon:设置线程是否成为守护线程和判断线程是否是守护线程,守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
currenThread():静态方法,获取当前线程
新建(new)
可运行(runnable)——可运行的线程可能正在运行也可能没有运行
被阻塞(blocked)
等待(waiting)
倒计时等待(timed waiting)
被终止(terminated)
getState方法
阻塞:当线程试图获取一定内部的对象锁,而该锁被其它线程持有。
等待:当线程等待另一个线程通知调度器一个条件时
超时参数:调用它们导致线程进入计时等待,带有超时参数的方法有Thread.sleep、Object.wait、Thread.join、Lock.tryLock以及Condition.await的计时版
线程优先级、守护线程、线程组、处理未捕获异常的处理器
MAX_PRIORITY(10)
MIN_PRIORITY(1)
NORM_PRIORITY(5)
线程优先级高度依赖于系统
守护线程能够为其它线程提供服务
当系统只剩下守护进程是,虚拟机会退出,由于只剩下守护线程,就没必要继续运行程序了
线程的run方法不能抛出任何受查异常,但是,非受查异常会导致线程终止
UncaughtException的做法如下:
在进入临界区之前进行lock,在finally处必须要进程unlock操作,不能使用带资源的try
每一个Bank对象有自己的ReentrantLock对象,同时访问同一个对象时,锁以串行的方式提供服务
可以用newCondition方法获得一个条件对象,通过await()方法阻塞线程,通过signalAll()/signal()唤醒线程
signalAll()方法不会立即激活一个等待线程,它仅仅解除等待线程的阻塞,以便这些线程可以在当前线程退出同步方法之后,通过竞争实现对对象的访问
signal()方法会随机解除等待集中某个线程的阻塞状态
修饰实例方法 —— 作用于当前实例加锁,进入同步方法代码前要获得当前实例的锁
修饰静态方法 —— 作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁;
访问静态synchronized方法占用的锁是当前类的class对象,而访问非静态synchronized方法占用的锁是当前实例对象锁
修饰代码块 —— 指定加锁对象,对给定对象加锁,进入同步代码块钱要获得给定对象的锁
将synchronized作用于一个给定的实例对象instance,即当前实例对象就是锁对象
notifyAll():解除那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内使用
notify():随机选择一个在该对象调用wait方法的线程的阻塞状态,如果该线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException
如果向一个变量写入值,而这个变量接下来可能被另一个线程读取。或者。从一个变量读值,而这个变量可能是个之前被另一个线程写入的,此时必须使用同步。
不能确保翻转域中的值。不能保证读取、翻转和写入不被中断
final定义的变量其值一旦定义后是不能进行修改的,故能提供安全性访问
获得值、增1并设置然后生成新值的操作时不会中断的
自增1并后去增长后的值
基本类型:AtomicInteger、AtomicLong、AtomicBoolean
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
引用类型:AtomicReference、AtomicStampedReference、AtomicMarkableReference
对象的属性修改类型:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
因为乐观更新需要太多次重试
死锁的前提:互斥、持有、非抢占、循环等待
为了避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例
ThreadLocal辅助类为各个线程提供一个单独的生成器
线程在调用lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞
tryLock方法视图申请一个锁,在成功获得锁后返回true,否则,立即返回false
lock方法不能被中断,如果一个线程在等待获得一个锁是被中断,中断线程在获得锁之前一直处于阻塞状态,如果出现死锁,lock方法将无法被终止
tryLock可以调用超时参数,如果线程线程在等待期间被中断,将抛出InterruptedException异常
ReentrantLock
ReentrantReadWriteLock
stop方法终止所有未结束的方法,当线程被终止,立即释放被它锁住的所有对象的锁,会导致对象处于不一致的状态。当线程要终止另一个线程时,无法知道什么时候调用stop方法是安全的,什么时候导致对象被破坏。
suspend方法经常导致死锁,如果用suspend挂起一个持有锁的线程,该锁在恢复之前是不可用的,如果调用supend方法的线程视图获得同一个锁,那么程序死锁
共同点:都试图控制一个给定线程的行为;
不同点:stop会破坏对象,而suspend不会破坏对象
当视图向队列添加元素而队列已满,或是想从队列移出元素而队列为空的时候,阻塞队列导致线程阻塞
注意:
ArrayBlockingQueue
PriorityBlockingQueue
getDelay
TransferQueue
如果多线程要并发地修改一个数据结构,可能会破坏这个数据结构
意味着迭代器不一定能反映出它们被构造之后的所有的修改,但它们不会将同一个值返回两次,也不会抛出ConcurrentModificationException异常
即使有其他线程在处理映射,这些操作也能安全地执行、批操作会变量映射,处理变量过程中中熬到的元素,无需冻结当前映射的快照
搜索:为每个键或值提供一个函数,直到函数生成一个非null的结果。然后搜索终止,返回这个函数的结果
归约:组合所有键或值,这里要使用所提供的一个累加函数
foeEach:为每个键或值提供一个函数
operationKeys(处理键)
operationValues(处理值)
operation(处理键和值)
operationEntries(处理Map.Entry对象)
Runnable封装一个异步运行的任务,可以把它想象成为一个没有参数和返回值的异步方法
Callable有返回值,是一个参数化的类型,只有一个call方法
Future保存异步计算的结果
能将Callable转换成Future和Runnable
Future> submit(Runnable task)
Future> submit(Runnable task,T result)
Future> submit(Callable
为预定执行或重复执行任务而设计的方法
invokeAll方法提交所有对象到一个Callable对象的集合中,并返回某个已经完成了的任务的结果
缺点:
常规方法
ExecutorCompletionService service = new ExecutorCompletionService<>(executor);
for(Callable task:tasks) service.submit(task);
for( int i=0;i
每个处理器内核分别使用一个线程,来完成计算密集型任务
工作密取,每个工作现场都有一个双端队列来完成任务,一个工作线程将子任务压入其双端队列的队投。(只有一个线程可以访问队头,所以不需要加锁)。一个工作线程空闲是,它会从另一个双端队列的队尾”密取”一个任务。由于大的子任务都在队尾,这种密取很少出现
可以指定你希望做什么,以及希望以什么顺序执行这些工作
当两个线程在同一数据缓冲区的两个实例上工作的时候
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结 点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁 的分配。
AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该 同步状态进行原子操作实现对其值的修改。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
状态信息通过procted类型的getState,setState,compareAndSetState进行操作
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) protected final boolean compareAndSetState(int expect, int update) {
}
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、 CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某 一资源进行读。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方 式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
AQS全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
AQS是一个用来构建所和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符号我们自己需求的同步器。
同问题25.
单例模式了解一下
public class Singleton{
private volatile static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getUniqueInstance(){
//先判断对象是否已经实例过了,没有实例化过才进入加锁代码
if(uniqueInstance == null){
//类对象加锁
synchronized (Singleton.class){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分
为三步执行:
为 uniqueInstance 分配内存空间
初始化 uniqueInstance
将 uniqueInstance 指向分配的内存地址
由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在 多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
4.执行monitorexit的线程必须是objectref所对应的monitor的所有者
偏向锁:引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉。
轻量级锁:轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。
自旋锁和自适应自旋:
锁消除:虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间
锁粗化:编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。
功能区别
相同点:
都是加锁方式同步,而且都是阻塞式同步
不同点:
性能区别
synchronized:
ReentrantLock:
同问题58
不允许使用Executors去创建,而是通过ThreadPoolExecutor的方法,原因如下:
Executors返回线程池对象的弊端:
方式一:通过构造方法实现
方式二:通过Executor框架的工具类Executors来实现
CAS原理
参数:V 要更新的变量;E 预期值 ;N 新值
参数约束:
CPU指令:CAS整个操作过程是一个原子操作,它是由一条CPU指令完成的,从指令层保证操作可靠,不会被多线程干扰
无锁与volatile:
同问题36
AtomicInteger类常用方法
public final int get();//获取当前的值
public final int getAndSet(int newValue);//获取当前的值,并设置新的值
public final int getAndIncrement();//获取当前的值,并自增
public final int getAndDecrement();//获取当前的值,并自减
public final int getAndAdd(int delta);//获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update);//如果输入的数值等于预期的值,则以原子方式将该值设置为输入值(true)
public final void lazySet(int newValue);//最终设置为newValue,使用lazySet设置之后可能导致其他线程在之后一小段时间内还是可以读到旧的值
AtomicInteger类的使用示例
class AtomicIntegerTest{
private AtomicInteger count = new AtomicInteger();
public void increment(){
count.incrementAndGet();
}
public int getCount(){
return count.get();
}
}
AtomicInteger类的部分源码:
//setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static{
try{
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch(Exception ex){ throw new Error(ex);}
}
private volatile int value;
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset()方法 是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。