非公平锁
公平锁如何实现的呢?我们来看看 ReentranLock 中的 NonfairSync 类,这个类中就实现了对非公平锁的实现。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
这段逻辑中,我们可以发现,线程是直接取修改 state 的状态,这代表竞争锁,如果竞争到锁,就执行业务逻辑,如果竞争不到锁再执行 acquire 方法。不排队就是非公平锁。
公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁呢,就需要先看看队列中是否有等待的线程,如果没有才取竞争锁,如果等待的队列中有线程,则返回 false , 线程进入排队的逻辑。
可重入
先来探讨一下,在什么情况下需要锁的可重入性。我们现在知道了 sychronized 这个关键字修饰方法的方法,其实是在对象头上上的锁。所以下面代码中,
public class ReentranTest {
public static synchronized void add(){
System.out.println("add in");
update();
System.out.println("add out");
}
private static synchronized void update() {
System.out.println("update in ");
System.out.println("do something");
System.out.println("update out ");
}
public static void main(String[] args) {
add();
}
执行的结果如下所示:
add in
update in
do something
update out
add out
在执行某个操作的时候,需要先调用 add ,然后在 add 中,调用 update() 此时,同一个线程是可以进入的。如果 sychronized 底层代码没有考虑可重入性,那么当执行到 update() 的时候,状态(这里比较复杂,sychronized 涉及到了锁的升级,这个升级已经考虑了可重入性)已经被改变了,update 方法是不能进入的。我们拿 ReetrantLock 来说明吧,请看下面的例子,如下面的例子:
package interview;
import java.util.concurrent.locks.ReentrantLock;
public class ReentranTestV2 {
public static ReentrantLock lock = new ReentrantLock();
public static void add(){
lock.lock();
try {
System.out.println("add in");
update();
System.out.println("add out");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
private static void update() {
lock.lock();
try {
System.out.println("update in ");
System.out.println("do something");
System.out.println("update out ");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
add();
}
}
当执行当 update() 的时候,同一个线程会再次申请同一把锁,如果锁不能重入 ,则线程会死锁在 update() 代码里面。从 ReetrantLock 的 tryAquire() 代码中可以看到,如果不进行可重入处理,那么会返回 false,则进行入等待队列的环节,这样的话就自己被自己锁住了。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
条件队列
条件队列是一个进程间通信的问题。它是协调多个线程间行为的。例如,1A2B3C的问题。下面是代码,其中,前面两个都是解决 A B C 交题打印的问题,第三个是交替打印 100 次的逻辑。其实解题的思路都是以 flag 这个变量为核心,flag 好比指示灯,不同的指示灯亮起对应着响应线程的动作。具体到每个线程的逻辑就是,当竞争到锁的线程进入到逻辑代码中,先判断 flag 的值,符合条件则执行打印代码,否则执行等待。
import java.util.concurrent.CountDownLatch;
/**
* 使用的 sychronized 关键字的方式。
* 这个解题的思路的关键是什么时候该掉 wati 方法和 notify 方法。
* 当条件不满足的时候,则调用 wait 方法
* 当满足条件的时候,则执行逻辑代码,最后执行 notify 方法,唤醒其他等待着的线程
* */
public class A1B2C3 {
private static int flag = 0 ;
public static void main(String[] args) {
Object lock = new Object();
CountDownLatch cdl = new CountDownLatch(3);
Thread thread = new Thread(()->{
synchronized (lock){
while(flag != 0){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
flag = 1 ;
lock.notifyAll();
cdl.countDown();
}
});
Thread thread1 = new Thread(()->{
synchronized (lock){
while(flag != 1){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
flag = 2 ;
lock.notifyAll();
cdl.countDown();
}
});
Thread thread2 = new Thread(()->{
synchronized (lock){
while(flag != 2){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
lock.notifyAll();
cdl.countDown();
}
});
thread.start();
thread1.start();
thread2.start();
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 和 A1B2C3 的解题思路是一样的
* */
public class A1B2C3Version2 {
private static int flag = 0 ;
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
CountDownLatch cdl = new CountDownLatch(3);
Thread thread = new Thread(()->{
lock.lock();
try{
while(flag != 0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
flag = 1 ;
condition.signalAll();
cdl.countDown();
}finally {
lock.unlock();
}
});
Thread thread1 = new Thread(()->{
lock.lock();
try{
while(flag != 1){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
flag = 2 ;
condition.signalAll();
cdl.countDown();
}finally {
lock.unlock();
}
});
Thread thread2 = new Thread(()->{
lock.lock();
try {
while (flag != 2) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
condition.signalAll();
cdl.countDown();
}finally {
lock.unlock();
}
});
thread.start();
thread1.start();
thread2.start();
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 这个题目是,使用三个线程接替打印 0 , 1, 2 三个数字,最后的结果是:
* 0
* 1
* 2
* 0
* 1
* 2
* ...
* 0
* 1
* 2
* 这个思路其实和 A1B2C3 的思路差不多,在不符合条件的时候,执行 await 方法
* 然后等待其他线程执行完毕 sigianl 线程,唤醒当前线程。
* */
public class A1B2C3for {
private static int flag = 0 ;
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List<Integer> list = new ArrayList<Integer>();
Thread thread = new Thread(()->{
for(int i = 0 ; i < 100 ; i++){
lock.lock();
try{
while (flag != 0 ){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(flag);
flag = 1 ;
condition.signalAll();
}finally {
lock.unlock();
}
}
});
Thread thread2 = new Thread(()->{
for(int i = 0 ; i < 100 ; i++){
lock.lock();
try{
while (flag != 1 ){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(flag);
flag = 2;
condition.signalAll();
}finally {
lock.unlock();
}
}
});
Thread thread3 = new Thread(()->{
for(int i = 0 ; i < 100 ; i++){
lock.lock();
try{
while (flag != 2 ){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(flag);
flag = 0;
condition.signalAll();
}finally {
lock.unlock();
}
}
});
thread.start();
thread2.start();
thread3.start();
try {
thread.join();
thread.join();
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0 ; i < 300 ; i++){
if(i%3 != list.get(i) ){
System.out.println("false , index:" + i);
}
}
}
}