公平锁和非公平锁:
所谓公平锁就是各个线程争抢锁的时候要按照申请锁的顺序来,不能随意加塞,类似排队打饭 先来后到。
非公平锁则相反,谁先抢到锁,谁就先执行,没有抢到,才按照申请锁的顺序来执行。有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象。
公平锁/非公平锁的创建:
并发包ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁 默认是非公平锁。
如:
new ReentrantLock();
new ReentrantLock(fasle);
//都是非公平锁
new ReentrantLock(true);
//公平锁
就Java ReentrantLock而言,
通过构造函数指定该锁是否是公平锁 默认是非公平锁 非公平锁的优点在于吞吐量比公平锁大。
synchronized也是一种非公平锁.
可重入锁(递归锁):
可重入锁就是如果拿到锁进入A方法内,而A方法内又调用了另一个同步方法B,那么这个线程并不是拿到了两个同步方法的两把锁,而仅仅是刚开始拿到的一把锁。
即:同一线程外层函数获得锁之后,内层函数仍然能获取该锁的代码。在同一个线程方法外层获取锁的时候,在进入内层方法会自动获取该锁。
典型的可重入锁:
ReentrantLock和synchronized都是可重入锁。
可重入锁的作用:
最大的作用就是避免死锁。
class Phone{
public synchronized void sendSms() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendSms");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendEmail");
}
}
public class ReenterLockDemo {
/**
* t1 sendSms
* t1 sendEmail
* t2 sendSms
* t2 sendEmail
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
输出:
t1 sendSms
t1 sendEmail
t2 sendSms
t2 sendEmail
自旋锁:
自旋锁就是指获取锁的线程不会立即阻塞,而是通过循环判断的方式去获得锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗cpu。
直接举例,使用CAS的方法手写一个自旋锁:
public class SpinLockDemo {
//常见线程的原子引用
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() ->{
spinLockDemo.myLock();
//等待5秒,便于其他线程进入并验证效果
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.MyUnLock();
}, "AA").start();
//保证AA先执行
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
//BB直接执行
new Thread(() ->{
spinLockDemo.myLock();
spinLockDemo.MyUnLock();
}, "BB").start();
}
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+" come in");
//如果不是null,在这里死循环,实现lock的效果
while(!atomicReference.compareAndSet(null, thread)){
}
}
public void MyUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+" invoked unlock");
}
}
执行结果:
public class SpinLockDemo {
AtomicReference atomicReference = new AtomicReference<>();
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() ->{
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.MyUnLock();
}, “AA”).start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() ->{
spinLockDemo.myLock();
spinLockDemo.MyUnLock();
}, "BB").start();
}
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+" come in");
//如果不是null,在这里死循环,实现lock的效果
while(!atomicReference.compareAndSet(null, thread)){
}
}
public void MyUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+" invoked unlock");
}
}
AA come in
BB come in
//下面一BB一定要等AA执行完毕才能执行完。
AA invoked unlock
BB invoked unlock
独占锁:
该锁一次只能被一个线程锁持有。ReentrantLock和synchronized都是独占锁。
共享锁:
指该锁可以被多个线程共享。
ReentrantLock的写是独占锁,但是读是共享的。可以提高并发的效率。
读写锁:
之前的juc笔记中有。