一、聊聊你理解的并发编程?
- 简单来说并发编程就是,在某一时刻多个线程去访问同步资源。
- 因为资源有限,所以存在竞争和分配资源的问题。
- 在这种情况下如果不对其加以控制,很有可能会出现不可控的问题。
- 为了保证并发的安全性,java引入了sychronized锁,通过对资源进行加锁来实现并发安全,sychronized是重量级锁,底层是通过内存屏障实现加锁的,是指令层面的锁。之后又引入了Lock,通过cas操作和版本控制等来实现并发安全。Lock是代码层面的锁。在jdk1.6对Sychronized锁进行了优化,引入了偏向锁,轻量级锁,重量级锁,自旋锁,适应性自旋锁。
二、聊聊你理解的volatile。
- volatile可以使修饰的变量在线程之间可见—可见性。
- volatile可以防止指令重拍—有序性。
- volatile还可以触发缓存一致性协议—可见性。
三、对Synchronized的理解(锁升级啥的)
- Synchronized是指令级别的锁,底层是通过内存屏障实现的。monitor-enter~monito-exit
- 在jdk1.6对Synchronized的锁进行了优化,引入了很多种锁状态,无锁、偏向锁、轻量级锁、重量级锁。这些锁状态表示资源竞争的激烈程度。
- 多线程中只有一个线程可以获取同步资源,其他线程在同一时刻只能有一个线程去访问资源—无锁,这个同一操作到达一定次数则升级成偏向锁。如果同一时刻有多个线程去访问同步资源则升级成轻量级锁,当这些线程自旋到一定次数则升级成重量级锁。把这些未获取资源的线程放入阻塞队列。
四、volatile和synchronized的区别
- volatile和synchronized都可以保证可见性和可以防止指令重排。
- volatile不能保证修饰变量的线程安全。
- synchronized可以保证线程安全。
- volatile修饰的是变量,synchronized可以修饰成员方法、代码块、静态方法。
五、JUC包下的工具用过哪些?
- 线程池:
- FixedThreadPool、确定最大线程数,慢
- CachedThreadPool、无最大线程数,max=2^ 32-1,快,但是不建议使用
- SingleThreadExecutor、逐个执行。慢的不行
- ThreadPoolExecutor、自定义线程池。
- 同步器:
- CountDownLatch(闭锁):
- 底层结构,定义了一个Syn内部类继承AQS同步器。
- 有一个count参数,当count等于0的队列中的线程会出队,并且全部执行。调用countDown()相当于count—;调用await()方法则当前线程进入同步阻塞队列。
- 适用场景:线程个数达到count个,就开始执行。
- Semaphore(信号量)
- 用于有限资源的分配。
- 适用场景:地铁站排队买票。机器有限。
- CyclicBarrier(循环障碍):
- 适用场景:公交车,一辆公交车坐满了就出发。count可以重置。
- CountDownLatch(闭锁):
六、as-if-serial和happens-before语义。
as-if-serial 表示指令不管怎么重排,不能导致预期结果出现不可控问题。
happens-before八大原则:
- 程序次序规则:线程内按程序书写顺序执行,书写在前的先行发生书写在后的操作。
- 锁定规则:对于同一个锁的操作,unlock操作先行发生于lock操作。
- volatile规则:对于一个变量的写先行发生于对这个变量的读。
- 传递规则:A先行发生于B,B先行发生于C;则可以推出A现行发生于C。
- 线程启动原则:start的方法先行发生于该线程的每一个动作。
- 线程中断原则:interrupt方法调用先行发生于中断的代码检测事件。
- 线程终结原则:线程中的所有的操作都先行发生于线程终止检测。我们可以通过Thread.join 方法结束。Thread.isAlive的返回值手段检测到线程是否已经终止执行。
- 对象终结原则:一个对象的初始化先行发生于其finalize方法的调用。
七、AQS 和 ReentrantLock的区别。
- AQS是一个抽象队列同步器,这是Doug Li 写的一个抽象类,方便我们自定义同步器。
- AQS继承了AbstractOwnableSynchronizer、并且实现了可序列化接口。
- AQS中有几个比较重要的成员属性:
- 有一个双向链表存储阻塞线程,head指向队头,tail指向队尾。
- State信号量,用于表示同步锁的状态。
- 一个Node内部类,是AQS中双向链表的节点,Node中存储了Thread,waitStatus表示当前节点的状态。
- ReentrantLock是一个实例类。他实现了Lock接口,拥有tryLock和unlock功能。在内部定义了一个Syn抽象内部类,该内部类继承了AQS,还定义了两个锁,一个公平锁,一个非公平锁。这两个锁都是继承Syn这个内部类。
八、java中线程生命周期
- NEW:未调用start()方法的线程对象。
- Runnable:正在JVM中执行的线程或者正在等待系统调度。
- Blocked:竞争monitor锁,未竞争到,则进入Blocked态,处于Blocked状态的线程,需要等待monitor lock 的释放然后进入同步块,或者调用Object.wait方法重新进入同步块。
- Waiting:调用以下方法会进入waiting状态。
- Object.wait 但是未超时。
- Thread.join 但是未超时。
- LockSupport.park
- Timed_Waiting:调用以下方法会进入Timed_Waiting状态。
- Thread.sleep
- Object.wait 超时。
- Thread.join 超时
- LockSupport.parkNanos
- LockSupport.parkUntil
- Terminated:当线程执行完毕则进入Terminated状态。
九、聊聊你对CAS的理解
CAS的全称是Compare And Swap ,直译就是比较与交换。是一条CPU原子指令,起作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。其实现方式是基于硬件平台的汇编指令,比如在intel的CPU中,使用的是cmpxchg指令,也就是说CAS是靠硬件实现的,从而在硬件层面提升效率。
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁,JDK中大量使用了CAS来更新数据替代了加锁来保持原子更新。
CAS操作包含三个操作数:内存偏移量位置、预期值和新值。如果内存位置的值和预期值相匹配,那么处理器会自动将该位置更新为新值。如果内存位置的值与预期值不匹配,则认为该次操作是过期的,处理器不做任何操作。
CAS的缺点
- 只能保证对一个变量的原子性操作
- 长时间自旋会给CPU带来压力(通过重量级锁来解决)
- ABA问题(通过加一个版本号来解决)
十、JMM模型知道吗?
JMM描述的是一种抽象的概念,一组规则,通过这组规则来控制程序中各个变量在共享区域和私有区域的访问方式,JMM是围绕原子性、有序性、可见性展开的。
十一、java中的锁有哪些?
- 线程是否需要锁住同步资源:
- 是—悲观锁。
- 否—乐观锁。
- 获取锁失败线程是否阻塞:
- 否—自选锁、适应性自选锁。
- 是
- 多个线程竞争同步资源的流程:
- 不锁住同步资源,多个线程中只有一个能修改成功,其他线程会重试—无锁。
- 同一个线程执行同步资源时,自动获取资源—偏向锁
- 多个线程竞争同步资源,未获取到资源:
- 自旋等待锁释放—轻量级锁
- 阻塞等待唤醒—重量级锁
- 多线程竞争锁时是否需要排队
- 是—公平锁。
- 否—非公平锁。
- 同一个线程的多个流程是否能获取同一把锁:
- 是—可重入锁。
- 否—不可重入锁。
- 多个线程能否共享一把锁:
- 能—共享锁。
- 否—排他锁。