什么是java的公平锁呢,就是很公平,在并发环境中,每个线程在获取锁时会先看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己,多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到,非公平锁呢,是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象,非公并锁比较粗鲁,上来就直接尝试占有锁,如果并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁,看一下ReentrantLock的构造函数,默认的构造函数是创建非公平锁,构造函数中指定true的时候是创建公平锁。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}/**
* 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();
}
那可重入锁呢,也称为递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。概念是抽象的,我们来看一段代码吧,要做的事情呢,就是一个资源类Phone中有两个加锁的同步方法即synchronized 修饰的sendSMS和sendEmail方法,其中sendSMS方法中调用sendEmail方法,然后再main方法中,启动两个线程t1和t2来调用sendEmail方法,看看执行结果。
public class ReentrantLockDemo {
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();}
}
class Phone {
public synchronized void sendSMS() throws Exception {
System.err.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();
}public synchronized void sendEmail() throws Exception {
System.err.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
}
}
从执行结果来看,一个线程执行完成后,另一个线程才能执行,这就说明,当一个线程获取到外层的方法锁的同时,它会自动获取内层方法的锁,也说明synchronized是可重入锁。
t1 invoked sendSMS()
t1 invoked sendEmail()
t2 invoked sendSMS()
t2 invoked sendEmail()
synchronized是可重入锁,那ReentrantLock呢,我们继续再来看一段代码,也是资源类Phone中,有一个get和set方法,这里不同的是,get和set方法不是synchronized来修饰,而是ReentrantLock类来加锁,get方法中调用set方法,还是在启动t3和t4两个线程,调用get方法,来看看执行结果。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo {
public static void main(String[] args) throws Exception {
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();Thread.sleep(1000);
Thread t3 = new Thread(phone, "t3");
Thread t4 = new Thread(phone, "t4");t3.start();
t4.start();
}}
class Phone implements Runnable {
public synchronized void sendSMS() throws Exception {
System.err.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();
}public synchronized void sendEmail() throws Exception {
System.err.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
}Lock lock = new ReentrantLock();
@Override
public void run() {get();
}private void get() {
lock.lock();
try {
System.err.println(Thread.currentThread().getName() + "\t invoked get()");
set();
} finally {
lock.unlock();
}
}private void set() {
lock.lock();
try {System.err.println(Thread.currentThread().getName() + "\t invoked set()");
} finally {
lock.unlock();
}
}
}
从执行结果来看,和synchronized是一样的效果,当一个线程拿到外层方法的锁后,也同时拿到内层方法的锁,说明ReentrantLock也是可重入锁
t1 invoked sendSMS()
t1 invoked sendEmail()
t2 invoked sendSMS()
t2 invoked sendEmail()
t3 invoked get()
t3 invoked set()
t4 invoked get()
t4 invoked set()
我们再来回顾下可重入锁的概念,也称为递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块,下篇见。