乐观锁VS悲观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
乐观锁:假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
synchronized初始使用乐观锁策略。当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略
读写锁
多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。读写锁,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。
synchronized不是读写锁
重量级锁VS轻量级锁
synchronized开始是轻量级锁,如果锁冲突比较严重,就会变成重量级锁
自旋锁(挂起等待锁)
线程在抢锁失败后进入阻塞状态,放弃CPU,需要过很久才能再次被调度
如果获取锁失败,立即再尝试,无限循环,直到获取锁为止,第一次获取锁失败,第二次的尝试会在极短的时间内到来
公平锁VS非公平锁
操作系统内部的线程调度就可以视为是随机的。如果不做限制,锁就是非公平锁。如果要实现公平锁,就需要依赖额外的数据结构。来记录线程们的先后顺序
synchronized是非公平锁
可重入锁VS不可重入锁
可重入锁:允许同一个线程多次获取同一把锁
(比如一个递归函数里有加锁操作,递归过程中锁不会阻塞自己,即为可重入锁)
Synchronized是可重入锁
面试题1》讲解一下你自己理解的CAS机制
全称Compare and swap,既“比较并交换”。相当于通过一个原子的操作,同时完成“读取内存,比较是否相等,修改内存”这三个步骤
2》ABA问题怎么解决?
给要修改的数据引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期。如果当前版本号和之前读到的版本号一致,就真正执行修改操作,并让版本号自增;如果发现当前版本号比之前读到的版本号大,就认为操作失败
Synchronized原理
基本特性:1.开始时是乐观锁。如果锁冲突频繁,就转换为悲观锁
2.开始是轻量级锁实现,如果锁被持有的时间较长,就换成重量级锁
3.实现轻量级锁的时候大概率用到自旋锁策略
4.是不公平锁,可重入锁,不是读写锁
1》什么是偏向锁?
偏向锁不是真的加锁,而只是在锁的对象头中记录一个标记(记录该锁所属的线程)。如果没有其他线程参与竞争锁,那么就不会真正执行加锁操作,从而降低程序开销。一旦真的涉及到其他的线程竞争,再取消偏向锁状态,进入轻量级锁状态
介绍下Callable是什么?
Callable是一个interface.相当于把线程封装了一个“返回值”。方便开发者借助多线程的方式计算结果
Callable和Runnable相对,都是描述一个“任务”,Callable描述的是带有返回值的任务,Runnable描述的是不带返回值的任务
Callable通常需要搭配FutureTask来使用。FutureTask用来保存Callable的返回结果,因为Callable
往往是另一个线程中执行的,啥时候执行完并不确定。FutureTask就可以负责这个等待结果出来的工作
面试:1》线程同步的方式有哪些?
synchronized,ReentrantLock,Semaphore等都可以用于线程同步
2》为什么有了synchronized还需要juc下的lock?
以juc的ReentrantLock为例
synchronized使用时不需要手动释放锁。ReentrantLock使用时需要手动释放。使用起来更灵活;synchronized在申请锁失败时,会死等。ReentrantLock可以通过tyrlock的方式等待一段时间就放弃;synchronized是非公平锁,ReentrantLock默认是非公平锁。可以通过构造方法传入一个true开启公平锁模式;synchronized是通过Object的wait/notify实现等待/唤醒。每次唤醒的是一个随即等待的线程。ReentrantLock搭配Condation类实现等待-唤醒,可以更精确控制唤醒某个指定的线程
面试:1》ConcurrentHashMap的读是否要加锁,为什么?
读操作没有加锁。目的是为了进一步降低锁冲突的概率。为了保证读到刚修改的数据,搭配了volatile关键字
2》介绍下ConcurrentHashMap的锁分段技术?
简单说就是把若干个哈希桶分成一个“段”,针对每个段分别加锁。目的也是为了降低锁竞争的概率,当两个线程访问的数据恰好在同一个段上的时候,才触发锁竞争
3》Hashtable和HashMap、ConcurrentHashMap之间的区别?
HashMap:线程不安全。Key允许为null
Hashtable:线程安全。使用synchronized锁Hashtable对象,效率较低,key不允许为null
ConcurrentHashMap:线程安全。使用synchronized锁每个链表头结点,锁冲突概率低,充分利用CAS机制。优化了扩容方式。Key不允许为null
死锁:多个线程同时被阻塞,他们中的一个或者全部都在等待某个资源被释放,由于线程被无限期地阻塞,因此程序不可能正常终止
如何避免死锁:
1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
4.循环等待,即存在一个等待队列;
面试:1》谈谈volatile关键字的用法?
volatile能够保证内存可见性。强制从主内存中读取数据,此时如果有其他线程修改被volatile修饰的变量,可以第一时间读取到最新的值
2》Java多线程是如何实现数据共享的?
JVM把内存分成了这几个区域:
方法区、堆区、栈区、程序计数器。
其中堆区这个内存区域是多个线程之间共享的
只要把某个数据放到堆内存中,就可以让多个线程都能访问到
3》Java创建线程池的接口是什么?