各种锁的理解
公平锁与非公平锁
公平锁:非常公平,不能够插队,先来后到
非公平锁:可以插队,比较灵活(默认都是非公平,如: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 {// 底层使用的是CASDemo03 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。
思考:为什么要让锁一直自旋判断呢?