公平锁
是指多个线程按照申请锁的顺序来获取锁类似排队打饭 先来后到
非公平锁
是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象
日常见到的比较多的两种锁,也就是synchronized/ReentrantLock,synchronized是非公平锁,而ReentrantLock可以通过构造函数指定该锁是否是公平锁 默认是非公平锁 非公平锁的优点在于吞吐量比公平锁大.
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
public class ReenterLockDemo {
static class Phone {
public synchronized void sendSms() {
System.out.println(Thread.currentThread().getName() + "运行了sendSms方法");
sendEmail();
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "运行了sendEmail方法");
}
}
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方法
这说明了synchronized是一个可重入锁,例子中线程在执行sendMsm方法的时候就已经持有该对象的锁了,所以在进入内层的同步方法sendEmail时不必再次请求该对象锁,可以直接执行。
再看一下ReentrantLock,
public class ReenterLockDemo1 {
public static void main(String[] args) {
Phone phone = new Phone();
Thread thread1 = new Thread(phone, "t1");
thread1.start();
Thread thread2 = new Thread(phone, "t2");
thread2.start();
}
static class Phone implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "执行了get方法");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "执行了set方法");
} finally {
lock.unlock();
}
}
}
}
执行结果
t1执行了get方法
t1执行了set方法
t2执行了get方法
t2执行了set方法
可以说明ReentrantLock也是可重入锁。
自旋锁:自旋是一种“无锁式”的同步方法,CAS就是一种自旋的思想,它是指线程会采用循环的方式来获取锁,这个过程中线程不会阻塞,这也就避免了操作系统从用户态切换到内核态的切换,提高了性能。
public class SpinLock {
//此时原子引用中的引用对象为空
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void mySpinLock() {
//获取当前线程
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null, thread)) {
}
System.out.println(thread.getName() + "获取了锁");
}
public void mySpinUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(thread.getName() + "释放了锁");
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
new Thread(() -> {
spinLock.mySpinLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.mySpinUnLock();
}, "t1").start();
//将主线程暂停一秒保证是t1先获取到锁
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLock.mySpinLock();
spinLock.mySpinUnLock();
}, "t2").start();
}
}
在上面的demo中,t1线程更改了主内存中的引用后暂停了5秒,这5秒里t2线程不断的执行CAS操作都不能成功,也就不断的循环,直到t1线程执行了Unlock方法将引用重新改为null,t2线程才可以跳出循环获取到锁。可以发现上面的方法中都没有synchronized,同样也能达到同步的效果。