Java并发——LockSupport、Condition源码解析

    写在前面,阅读这篇文章,需要这些知识:
    Java并发——Thread类解析、线程初探
    Java并发——CAS原子操作
    Java并发——AQS框架详解

LockSupport

    LockSupport类是一个工具类,用来在显示锁里面替换Object类的waitnotify方法的。在内置锁里面,wait方法必须在线程持有锁的时候才能对线程进行阻塞,但是LockSupport类却不一样,即使线程没有持有锁,它也能将该线程阻塞,它有两个重要的方法,但是底层的实现是由Unsafe类来实现的,也就是说,是由JVM来实现的:

public static void park() {
        UNSAFE.park(false, 0L);
    }
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
}

    park方法是阻塞线程,而unpark方法是唤醒被park方法阻塞的线程,如下代码:

public class Test {
    public static void main (String args[]) throws InterruptedException {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("蕾姆被障碍挡住啦");
                LockSupport.park();
                System.out.println("拉姆帮蕾姆清扫了障碍");
            }
        });
        thread.start();
        Thread.sleep(3000);
        System.out.println("三秒后拉姆来帮蕾姆清扫障碍了");
        LockSupport.unpark(thread);
    }
}
//打印:
//蕾姆被障碍挡住啦
//三秒后拉姆来帮蕾姆清扫障碍了
//拉姆帮蕾姆清扫了障碍

    从代码结果看出LockSupport阻塞了线程,三秒后由unpark方法唤醒接着运行。LockSupport阻塞线程的实质是以唯一一个许可证来完成的,unpark方法是给线程一个许可,而park方法是回收线程的许可,如果没有许可则陷入阻塞,不管调用几个unpark方法,线程中的许可始终只有一个。出于这个特性,unpark方法甚至可以在park方法之前调用。还有一点与wait方法不同,它阻塞的时候不会释放锁

public static void main (String args[]) throws InterruptedException {
        Lock lock=new ReentrantLock();
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("蕾姆被障碍挡住啦");
                LockSupport.park();
                System.out.println("拉姆帮蕾姆清扫了障碍");
                lock.unlock();
            }
        });
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("拉姆来帮蕾姆清扫障碍了");
                LockSupport.unpark(thread);
                lock.unlock(); 
            }
        });
        thread.start();
        thread1.start();
//只输出:蕾姆被障碍挡住啦

    输出完蕾姆被障碍挡住啦后,两个线程都陷入阻塞的状态,可见,确实没有释放掉锁。

Condition

    Condition在Java中是用于多线程协调通信的工具类,一般用于显示锁,即ReetrantLock的通信,与LockSupport类一样,提供了await方法、signal方法以及signalAll方法对应,Object类的waitnotifynotifyAll方法。以下面的代码为例:

public class Test {
    public static void main (String args[]) throws InterruptedException {
        Lock lock=new ReentrantLock();
        Condition condition=lock.newCondition();
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("蕾姆被障碍挡住啦");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("拉姆帮蕾姆清扫了障碍");
                lock.unlock();
            }
        });
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("拉姆来帮蕾姆清扫障碍了");
                condition.signal();
                lock.unlock();
            }
        });
        thread.start();
        thread1.start();
    }
}
//输出:
//蕾姆被障碍挡住啦
//拉姆来帮蕾姆清扫障碍了
//拉姆帮蕾姆清扫了障碍

    Condition的创建时依赖于ReentrantLock可重入锁的,因为Condition本质是一个接口,它的实现交由AQS框架完成的,如果对AQS框架不熟的同学可以看看这篇文章:Java并发——AQS框架详解。从上述例子中可以看出,它与Object类的wait方法一样是在阻塞的过程中释放锁的。在AbstractQueuedSynchronizer类中可以找到await方法源码:

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //d对当前线程包装,设置为CONDITION状态,加入等待队列
            Node node = addConditionWaiter();
            //释放当前线程占有的锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //释放完毕后,遍历AQS的队列,看当前节点是否在队列中,
        	// 不在说明它还没有竞争锁的资格,所以继续将自己沉睡。
        	// 直到它被加入到队列中,signal时加入同步队列
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                //检查是否正在阻塞
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //等待重新加入同步队列,请求资源
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

    AQS框架维护了一个同步队列,当持有锁的线程调用await方法时,AQS会把这个等待被唤醒的线程移除该队列,加入等待队列中,并调用LockSupport类的park方法使其陷入阻塞。当调用signal方法时,会把该线程重新加入同步队列,与其它线程抢夺资源。signal方法源码如下:

public final void signal() {
			//判断是否是独占锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
                //唤醒第一个等待队列的线程
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

    signal方法与notify方法不同,它总是唤醒第一个处于等待队列中的线程,而notify是随机的唤醒处于等待队列中的线程。doSignal方法底层调用了transferForSignal方法,该方法让处于CONDITION状态的线程,变为0状态并使其从等待队列中加入同步队列,去重新和其它线程竞争,且调用unpark方让线程恢复运行,signalAll原理一样。Condition另外一个比wait方法的优势在于,它可以创建多个等待队列来管理线程,它们互不干扰。而wait只有一个等待队列,也就是说Conditionwait方法要灵活。比如有如下场景,线程中有读取数据以及写入数据两部分,当存贮满的时候,这时候你肯定不希望写入,利用Condition则可以选择唤醒读取那部分而不是写入。如下代码:

public class Test {
    public static void main (String args[]) throws InterruptedException {
        Lock lock=new ReentrantLock();
        Condition read=lock.newCondition();
        Condition write=lock.newCondition();
        boolean readStatus=true;
        boolean writeStatus=true;
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("拉姆准备写数据啦");
                try {
                    write.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("拉姆写入数据了");
                lock.unlock();
            }
        });
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("蕾姆准备读数据啦");
                try {
                    if (writeStatus){
                        write.signal();
                    }
                    read.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("蕾姆读到数据了");
                lock.unlock();
            }
        });
        thread.start();
        thread1.start();
    }
}

    通过自定义的状态可以随时的控制condition的唤醒,而内置锁则没有这么的灵活。

你可能感兴趣的:(Java并发,Java源码分析与思考)