Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)

1. LockSupport工具类

  • LockSupport工具类提供了最基本的线程阻塞和唤醒功能,是构建同步组件的基础工具。
  • LockSupport工具类定义了一组以park开头的阻塞方法,可以通过unpark()方法进行唤醒。
  1. void park(): 阻塞当前线程,使线程从Runnable状态转到Waiting状态。直到调用unpark()方法或响应中断,才能从park()方法返回
  2. void parkNanos(long nanos) : 阻塞当前线程一段时间,单位是纳秒,使线程从Runnable状态进入timed-Waiting状态。直到阻塞时间结束,或者调用unpark(),或者响应中断,才能从parkNanos(naos)方法返回.
  3. void parkUntil(long deadline):parkNanos(naos)方法不同的是,deadline指的是从1970年到deadline的毫秒数
  4. void unpark(Thread thread): 唤醒处于阻塞状态的线程,使线程从Waiting状态或者timed-Waiting状态回到Runnable状态
  • 为了弥补Java 5遗漏了线程的阻塞对象这一问题,Java 6中的LockSupport工具类,提供了带阻塞对象的阻塞方法。
  1. 包括void park(Object blocker)void parkNanos(Object blocker, long nanos)void parkUntil(Object blocker, long deadline),参数blocker表示当前线程在等待的对象,简称阻塞对象。
  2. 通过在阻塞方法中携带阻塞对象,可以为程序员提供更多的现场信息,方便问题的定位。

2. Condition接口

① 对象的监视器方法与Condition接口
  • 在synchronized实现同步时,每个Java对象都有monitor对象与之绑定,通过进入和退出Monitor对象实现方法同步或者代码块同步。
  • 每个Java对象都拥有一组监视器方法:wait()wait(timeout)notify()notifyAll(),这些监视器方法synchronized关键字配合,实现等待/通知模式
  • Condition接口也提供了类似Object的监视器方法,与Lock配合实现等待/通知模式
② Condition接口与示例
  • Condition对象由Lock对象的newCondition()方法创建,即Condition对象与Lock对象是绑定的,而且一个Lock对象可以绑定多个Condition对象。
  • Condition对象的一般使用方法:
  1. 一般会将Condition对象作为成员变量,调用Condition对象的await()方法,当前线程会释放锁并进入等待状态
  2. 直到其他线程调用Condition对象的singal()方法或者singalAll()方法,当前线程才会从await()方法返回,并且在返回前一定获取了Condition对象关联的锁。
  • Condition接口的常用API:await()await(long time, TimeUnit unit)awaitNanos(long nanosTimeout)awaitUntil(Date deadline)signal()signalAll()
  • 通过Condition创建一个有界队列(没有过多的考虑,只是实现了先进后出):
public class BoundedQueue {
    private Lock lock;
    private Condition notFull;
    private int count;
    private Condition notEmpty;
    private Object[] items;

    public BoundedQueue(int size) {
        lock = new ReentrantLock();
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
        items = new Object[size];
        count = 0;
    }

    public void add(Object t) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await();
            }
            items[count] = t;
            count++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
    public Object remove() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            Object t = items[--count];
            notFull.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }
}
  1. 创建固定大小的队列,初始化Lock对象,创建两个Condition对象:notFull用于实现队列满/非满的等待/通知模式。notEmpty用于实现队列空/非空的等待/通知模式。
  2. 添加元素时,如果发现队列已满,则调用notFull.await(),让当前线程等待队列变得非满。直到退出while循环,然后可以向队列中添加元素。
  3. 向队列中添加元素后,调用notEmpty.signal(),告诉等待从队列中获取元素的线程,现在可以获取元素了。
  4. 获取元素是,如果发现队列为空,则调用notEmpty.await(),让当前线程等待队列非空。直到退出while循环,然后可以从队列中移除元素。
  5. 从队列中移除元素后,调用 notFull.signal() ,告诉等待向队列添加元素的的线程,现在可以添加元素了。
② Condition对象的等待队列
  • Condition对象中有一个等待队列,它是一个由链表组成的FIFO单向队列。有两个引用:firstWaiterlastWaiter,分别用于指向等待队列中的头节点和尾节点。
    Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)_第1张图片
  • 等待队列的节点添加和移除:
  1. 如果一个线程调用了condition对象的await()方法,则该线程会释放锁,构成节点加入到等待队列中,并进入等待状态。
  2. 如果其他线程调用了该condition对象的signal()方法,则会唤醒在等待队列中等待最久的节点(队列的头节点)。
  • 同步器中,ConditionObject是其内部类,同步器不仅拥有自己的同步队列,还有ConditionObject等待队列
  • 注意: Java对象的监视器模型中,一个Java对象只有一个同步队列和一个等待队列;而AQS中可以拥有一个同步队列和多个等待队列。因为,AQS可以绑定多个Condition对象。
    Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)_第2张图片
  • 关于await()方法:
  1. 调用Condition对象的await()方法,要求当前线程已经获取到锁,即当前线程处于同步队列中的头节点。
  2. 当前线程调用Condition对象的await()方法后,会释放锁并把自己构造成新的节点,通过addConditionWaiter()方法加入到等待队列中。
  3. 从队列的角度看,调用await()方法时,相当于同步队列中的节点移动到等待队列中。

Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)_第3张图片

调用await()方法,当前线程进入等待队列

  • 关于signal()方法:
  1. 其他线程调用调用Condition对象的signal()方法,会唤醒等待队列中等待时间最长的节点(其实并未醒来),即等待队列中的头节点。
  2. 位于头节点的中的当前线程会从await()方法中返回,在返回之前它必须获取到了condition对象关联的锁。只有获取到锁,当前线程才会被唤醒(真正醒来)。
  3. 因此,当前线程又会被构造新的步节点并加入同步队列中,等待锁的获取。
  4. 从队列中的角度看,调用signal()方法时,节点从等待队列移动到同步队列。
    Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)_第4张图片
    调用singal()方法,节点从等待队列移动到同步队列
  • 关于singalAll()方法:等待队列中的所有节点都会执行一次singal()方法,即等待队列中的所有节点都会移动到同步队列中,并唤醒每个节点的线程

3. 几种阻塞和唤醒方法的比较

① Thread.join()方法
  • 在A线程汇总调用B.join()方法,会将A线程挂起,直到B线程运行结束。
  • 感觉就像排队缴费时,甘愿让某人先缴费,当直到他缴费完毕,自己再继续缴费。
  • 在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
  • 编程实例:
public class JoinUse {
    public static void main(String[] main) {
        Thread myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("myThread: 计数值为" + i);
                }
            }
        });
        myThread.start();
        try {
            myThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程:myThread运行结束");
    }
}

Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)_第5张图片

② wait()和notify()/notifyAll()
  • wait()notify()notifyAll(),属于Java对象自带的监视器方法不属于Thread类
  • synchronized关键字一起使用,构成等待/通知模式。
  • 当前线程调用wait() 方法使自己挂起,等待某个条件的满足。其他线程的运行使得条件满足是,会调用调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
  • 编程实例1:代码块同步。包含wait()notify()方法的两个线程中,同步对象应该是同一个对象,否则会报错:java.lang.IllegalMonitorStateException
public class WaitUse {
    private int shared = 0;

    public static void main(String[] args) {
        WaitUse waitUse = new WaitUse();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (waitUse) {
                    // 条件不满足,挂起当前线程
                    while (waitUse.shared != 100) {
                        try {
                            waitUse.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 条件满足被,当前线程被唤醒,打印shared的值
                    System.out.println("print线程:shared的值为" + waitUse.shared);
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (waitUse) {
                    // 让shared增加至100
                    while (waitUse.shared != 100) {
                        waitUse.shared++;
                    }
                    // 条件满足,唤醒挂起的线程
                    System.out.println("add线程:shared值为100,可以打印了");
                    waitUse.notify();
                }
            }
        }).start();
    }
}
  • 编程实例2:方法同步。在同一个类中定义两种同步方法,一个调用wait()方法,一个调用调用notify()方法。在主线程中创建该类的对象,然后定义不同的线程分别调用对象的同步方法。
public class WaitUse {
    private int shared = 0;

    public synchronized void print() {
        try {
            // 条件不满足,挂起当前线程
            while (shared != 100) {
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 条件满足被,当前线程被唤醒,打印shared的值
        System.out.println("print线程:shared的值为" + shared);
    }

    public synchronized void add() {
        // 让shared增加至100
        while (shared != 100) {
            shared++;
        }
        // 条件满足,唤醒挂起的线程
        System.out.println("add线程:shared值为100,可以打印了");
        notify();
    }

    public static void main(String[] args) {
        WaitUse waitUse = new WaitUse();
        new Thread(new Runnable() {
            @Override
            public void run() {
                waitUse.print();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                waitUse.add();
            }
        }).start();
    }
}

Java高并发之LockSupport工具类、Condition接口(自主实现有界队列)、几种方法的比较(jion、wait/notify、await/signal的实例,sleep和wait的比较)_第6张图片-

  • wait() 和 sleep() 的区别:
  1. 所属的对象: wait()方法是Object的普通方法sleep()方法是Thread的静态方法
  2. 是否会释放锁: wait() 方法会释放锁,sleep()方法 不会。
  3. 使用限制: wait()方法必须在同步方法或同步代码块中使用,使用前必须获取对象的锁;sleep()方法可以在任何地方使用,让当前线程休眠一段时间。二者都需要处理InterruptedException异常。
  4. 使用场景: wait()方法一般与notify()方构成等待/通知模式,用于进行线程间的通信;sleep()方法一般用于让当前线程休眠一段时间。
  5. 是否发生上下文切换: wait()方法不一定导致上下文切换,sleep()方法强制CPU进行上下文切换。
    sleep()
③ await()和signal()/signalAll()方法
  • await()signal()signalAll()JUC包Condition接口提供与Object类似的监视器方法,它必须由Lock对象newCondition()方法创建,因此,必须与Lock配合使用,构成等待/通知模式。
  • 当前线程调用await()方法,当前线程挂起,等待被其他线程唤醒;其他线程继续运行在条件满足时,调用signal()唤醒被挂起的线程。
  • await()方法可以指定当代条件,比wait()方法更灵活。
  • 编程实例见上文,使用condition实现有界队列
public static void main(String[] arg) {
    BoundedQueue queue = new BoundedQueue(4);
    for (int i = 1; i <= 4; i++) {
        try {
            System.out.println("添加元素" + i);
            queue.add("元素" + i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 添加第5个元素
            try {
                System.out.println("添加元素5");
                queue.add("元素5");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 循环从队列中获取元素
            for (int i = 1; i <= 5; i++) {
                try {
                    System.out.println("获取" + queue.remove());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

你可能感兴趣的:(java相关)