公平锁/非公平锁
概念:所谓公平锁,就是多个线程按照申请锁的顺序来获取锁,类似排队,先到先得。而非公平锁,则是多个线程抢夺锁,会导致优先级反转或饥饿现象。
区别:公平锁在获取锁时先查看此锁维护的等待队列,为空或者当前线程是等待队列的队首,则直接占有锁,否则插入到等待队列,FIFO原则。非公平锁比较粗鲁,上来直接先尝试占有锁,失败则采用公平锁方式。非公平锁的优点是吞吐量比公平锁更大。
synchronized
和juc.ReentrantLock
默认都是非公平锁。ReentrantLock
在构造的时候传入true
则是公平锁。
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁
ReetrantLock /Syn 都是可重入锁
可重入锁又叫递归锁,指的同一个线程在外层方法获得锁时,进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有锁的同步代码块。比如get
方法里面有set
方法,两个方法都有同一把锁,得到了get
的锁,就自动得到了set
的锁。
就像有了家门的锁,厕所、书房、厨房就为你敞开了一样。可重入锁可以避免死锁的问题。
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSMS(); //进入sendSMS同步方法
},"t1").start();
}
}
class Phone{
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName()+"\t invoke sendSMS()");
sendEmail(); //调用sendEmail同步方法
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName()+"\t invoke sendEmail()");
}
}
结果:t1 invoke sendSMS()
t1 invoke sendEmail()
自旋锁
所谓自旋锁,就是尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取。自己一直循环获取,就像“自旋”一样。这样的好处是减少线程切换的上下文开销,缺点是会消耗CPU。CAS底层的getAndAddInt
就是自旋锁思想。
public class SpinLockDeno {
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t coming in");
//被线程占有则进入循环,等待释放
while(!atomicReference.compareAndSet(null,thread))
{
}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t invoke myUnlock");
}
public static void main(String[] args) {
SpinLockDeno spinLockDeno = new SpinLockDeno();
new Thread(() -> {
spinLockDeno.myLock();
//等待5秒,让B线程也进入myLock中,等待A线程释放
try{ TimeUnit.SECONDS.sleep(5); }catch (Exception e){ e.printStackTrace(); }
spinLockDeno.myUnlock();
},"AA").start();
//暂停1秒
try{ TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.printStackTrace(); }
new Thread(() -> {
spinLockDeno.myLock();
spinLockDeno.myUnlock();
},"BB").start();
}
}
AA coming in
BB coming in
AA invoke myUnlock
BB invoke myUnlock
AA线程先进入 compareAndSet 方法,此时参数不为null,BB线程在while循环中等待,当AA线程释放锁时,参数为null,BB线程才可以进入。BB线程在等待空闲时就是进行自旋操作。
读写锁
读锁是共享的,写锁是独占的。juc.ReentrantLock
和synchronized
都是独占锁,独占锁就是一个锁只能被一个线程所持有。有的时候,需要读写分离,那么就要引入读写锁,即juc.ReentrantReadWriteLock
。
比如缓存,就需要读写锁来控制。缓存就是一个键值对,以下Demo模拟了缓存的读写操作,读操作get
方法使用了ReentrantReadWriteLock.ReadLock()
,写操作put
方法使用了ReentrantReadWriteLock.WriteLock()
。这样避免了写独占,实现了读共享。
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantLock lock = new ReentrantLock();
public void put(String key,Object value){
rwLock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key);
try{ TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.printStackTrace(); }
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"\t 写入完成");
}catch (Exception e){
e.printStackTrace();
}finally{
rwLock.writeLock().unlock();
}
}
public void get(String key){
rwLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"\t 正在读取");
try{ TimeUnit.SECONDS.sleep(1); }catch (Exception e){ e.printStackTrace(); }
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
}catch (Exception e){
e.printStackTrace();
}finally{
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <=5 ; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt+"",tempInt+"");
}, String.valueOf(i)).start();
}
for (int i = 1; i <=5 ; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(tempInt+"");
}, String.valueOf(i)).start();
}
}
}