各种锁的理解
公平锁与非公平锁
公平锁:非常公平,不能够插队,先来后到
非公平锁:可以插队,比较灵活(默认都是非公平,如:synchronized,lock)
// Lock lock = new ReentrantLock(); 不带参数的构造方法
public ReentrantLock() {
sync = new NonfairSync();
}
// Lock lock = new ReentrantLock(true); 带参数的构造方法
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁(递归锁)
当一个锁中还有一个锁时,线程不会放弃第一把锁,就像递归一样层层深入,一直持有锁。
可以类比成:当你拿到进入一个房子的锁,进入房子后,再拿到进入卧室的锁,进入卧室,此时,就算你已经拿到了卧室的锁,你也并没有放弃房子的锁,别人依然进不来房子。
synchronized版
package lock;
// Synchronized
public class Demo01 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
lock 版
package lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
public static void main(String[] args) {
phone2 phone2 = new phone2();
new Thread(()->{
phone2.sms();
},"A").start();
new Thread(()->{
phone2.sms();
},"B").start();
}
}
class phone2{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"sms");
call();// 这里也有锁
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();// 锁必须配对,否则会死锁
try {
System.out.println(Thread.currentThread().getName()+"call");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
运行结果如下:
从结果可以看到,即使A线程进入了call方法,也依然没有放弃sms方法的锁,B线程依然无法进入sms方法
自旋锁
一个自旋锁的源代码示例
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
自旋锁示例:
设计一个自旋锁:
package lock;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*/
public class Demo03 {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"-->my lock");
while(!atomicReference.compareAndSet(null,thread)){
}
System.out.println(Thread.currentThread().getName()+"--->has this lock");
}
// 解锁
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"-->my unlock");
atomicReference.compareAndSet(thread,null);
}
}
该自旋锁示例本质上使用了CAS操作,笔者在狂神说代码上加入了
System.out.println(Thread.currentThread().getName()+"--->has this lock");
测试类:
package lock;
import java.util.concurrent.TimeUnit;
public class TestDemo03 {
public static void main(String[] args) throws InterruptedException {
// 底层使用的是CAS
Demo03 lock = new Demo03();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.myUnlock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.myUnlock();
}
},"T2").start();
}
}
结果如下:
至此,读者可以看到,T1在打印“has this lock”之后,T2是无法持有该锁,它在不断地自旋,即进入while循环一直判断,我们来看一下这个代码在做什么
while(!atomicReference.compareAndSet(null,thread))
这条代码是actomicReference调用了一个compareAndSet方法(CAS),这个方法是用来判断,如果Thread为Null,则加入修改为thread,并返回true,!true == false,则会退出while循环,如果Thread为某个线程thread,则于CAS的预期值(null)不同,会返回false,!false == true,所以会进入循环,循环体内什么都没有,又会进入判断,周而复始,直到那个线程unlock,调用CAS将thread修改为null。
思考:为什么要让锁一直自旋判断呢?