u11-线程通信

178 阅读6分钟

1. 等待唤醒

概念: 生产消费模型(观察者设计模式)中有两种角色,一个是生产者,负责生产资源,一个是消费者,负责使用资源,该模型的特点是,当无资源时,消费者应该阻塞并等待生产,当有资源时,生产者应该阻塞并等待消费。

  • 生产消费模型方法:以下三个方法均来自Object类,且只能在同步代码区中使用:
    • 锁实例.wait():让某个线程等待,此时该线程会加入到等待队列进行等待。
    • 锁实例.notify():随机唤醒等待队列中的一个线程,不释放锁。
    • 锁实例.notifyAll():唤醒等待队列中的所有等待线程,不释放锁。
  • wait() vs sleep()
    • wait() 是Object类的成员方法,sleep() 是Thread类的静态方法。
    • wait() 释放了锁,其他线程可以进入同步代码块,sleep() 不释放锁。
    • wait() 必须在同步代码块中调用,sleep() 可以在任何地方调用。
    • wait() 可以被唤醒 notify()sleep() 只能等待计时结束或者被打断 interreput()

源码: /javase-advanced/

  • src: c.y.thread.communication.WaitNotifyTest

2. 停止线程

概念: 想停止一个线程,只能等待线程体自然结束,如果要停止一个挂起,如 wait()sleep() 状态的线程,则需要使用 interrupt() 进行打断并获取一个异常,然后在异常处理 catch{} 中决定是否要终止线程,但是并不建议使用 interrupt() 来控制业务逻辑。

stop()方法已经过时,容易产生线程状态的不一致。

源码: /javase-advanced/

  • src: c.y.thread.communication.StopThreadTest
/**
 * @author yap
 */
public class WaitNotifyTest {

    @Data
    private static class Food {
        private String name;
        private String type;
        private boolean exist;
    }

    private static class AsyncProducer implements Runnable {

        private final Food food;
        private boolean isEnglish;

        AsyncProducer(Food food) {
            this.food = food;
        }

        @Override
        public void run() {
            while (true) {
                if (isEnglish) {
                    food.setName("cake");
                    food.setType("mickey");
                } else {
                    food.setName("dan-gao");
                    food.setType("mi-qi");
                }
                isEnglish = !isEnglish;
            }
        }
    }

    private static class AsyncConsumer implements Runnable {

        private final Food food;

        AsyncConsumer(Food food) {
            this.food = food;
        }

        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(food.getName() + ": " + food.getType());
            }
        }
    }

    private static class SyncProducer implements Runnable {

        private final Food food;
        private boolean isEnglish;

        SyncProducer(Food food) {
            this.food = food;
        }

        @Override
        public void run() {
            while (true) {
                // Don't use "synchronized (this)"
                synchronized (food) {
                    if (isEnglish) {
                        food.setName("cake");
                        food.setType("mickey");
                    } else {
                        food.setName("dan-gao");
                        food.setType("mi-qi");
                    }
                    isEnglish = !isEnglish;
                }
            }
        }
    }

    private static class SyncConsumer implements Runnable {

        private final Food food;

        SyncConsumer(Food food) {
            this.food = food;
        }

        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                synchronized (food) {
                    TimeUnit.MILLISECONDS.sleep(300L);
                    System.out.println(food.getName() + ": " + food.getType());
                }
            }
        }
    }

    private static class OneByOneProducer implements Runnable {

        private final Food food;
        private boolean isEnglish;

        OneByOneProducer(Food food) {
            this.food = food;
        }

        @SneakyThrows
        @Override
        public void run() {
            while (true) {
                synchronized (food) {
                    if (food.isExist()) {
                        food.wait();
                    } else {
                        if (isEnglish) {
                            food.setName("cake");
                            food.setType("mickey");
                        } else {
                            food.setName("dan-gao");
                            food.setType("mi-qi");
                        }
                        isEnglish = !isEnglish;
                        food.setExist(true);
                        food.notify();
                    }
                }
            }
        }
    }

    private static class OneByOneConsumer implements Runnable {

        private final Food food;

        OneByOneConsumer(Food food) {
            this.food = food;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (food) {
                    try {
                        if (food.isExist()) {
                            TimeUnit.SECONDS.sleep(1L);
                            System.out.print(food.getName() + ": " + food.getType() + "\n");
                            food.setExist(false);
                            food.notify();
                        } else {
                            food.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }

    private Food food = new Food();

    @Test
    public void asynchronousVersion() {
        new Thread(new AsyncProducer(food)).start();
        new Thread(new AsyncConsumer(food)).start();
    }

    @Test
    public void synchronizedVersion() {
        new Thread(new SyncProducer(food)).start();
        new Thread(new SyncConsumer(food)).start();
    }

    @Test
    public void oneByOneVersion() {
        new Thread(new OneByOneProducer(food)).start();
        new Thread(new OneByOneConsumer(food)).start();
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}



3. Condition

概念: Condition的本质就是多个等待队列,可以将线程放入指定的队列中,也可以从指定的队列中随机唤醒一个或者全部线程,Condition实例需要通过Lock接口的 newCondition() 来创建。

  • Condition方法:
    • void await():让线程进入指定的等待队列中。
    • void signal():从指定的等待队列中,随机唤醒一个 await() 状态的线程。
    • void signalAll():从指定的等待队列中,唤醒所有 await() 状态的线程。

源码: /javase-advanced/

  • src: c.y.thread.communication.ConditionTest
/**
 * @author yap
 */
public class ConditionTest {

    private static class ConditionDemo {
        private List<String> list = new ArrayList<>();
        private Lock lock = new ReentrantLock();
        private Condition producer = lock.newCondition();
        private Condition consumer = lock.newCondition();

        public void put(String data) {
            int max = 10;
            lock.lock();
            try {
                while (list.size() == max) {
                    producer.await();
                }
                list.add(data);
                consumer.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public String get() {
            String result = null;
            lock.lock();
            try {
                while (list.size() == 0) {
                    consumer.await();
                }
                result = list.remove(0);
                producer.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return result;
        }
    }

    @SneakyThrows
    @Test
    public void condition() {
        ConditionDemo conditionDemo = new ConditionDemo();

        for (int i = 0, j = 10; i < j; i++) {
            new Thread(() -> {
                for (int m = 0, n = 5; m < n; m++) {
                    System.out.println(Thread.currentThread().getName() + " got: " + conditionDemo.get());
                }
            }, "consumer-" + i).start();
        }

        TimeUnit.SECONDS.sleep(2L);

        for (int i = 0, j = 2; i < j; i++) {
            new Thread(() -> {
                for (int m = 0, n = 25; m < n; m++) {
                    conditionDemo.put(Thread.currentThread().getName() + ": " + m);
                }
            }, "producer-" + i).start();
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }

}

4. LockSupport

概念: LockSupport是并发包中提供的更灵活的控制线程等待和唤醒的工具类,它可以更精准地唤醒指定的某个线程。

  • LockSupport方法:
    • static void park():将当前线程停驻。
    • static void unpark(Thread thread):放行指定的线程。
  • unpack() 如果在 pack() 之前被调用,则 pack() 会失效。

源码: /javase-advanced/

  • src: c.y.thread.communication.LockSupportTest
/**
 * @author yap
 */
public class LockSupportTest {

    @SneakyThrows
    @Test
    public void lockSupport() {
        Thread thead = new Thread(() -> {
            for (int i = 0, j = 10; i < j; i++) {
                System.out.println(Thread.currentThread() + ": " + i);
                if (i == 5) {
                    LockSupport.park();
                }
            }
        });
        thead.start();

        TimeUnit.SECONDS.sleep(8L);
        System.out.println("after 8s...");
        LockSupport.unpark(thead);
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

5. CountDownLatch

概念: CountDownLatch,意为倒数的门闩,用来对线程进行倒数计数。

  • CountDownLatch关注的是门闩数,门闩数为0时放行,不关注线程数,一个线程可能或打开多个门闩。
  • CountDownLatch一般用于某个线程等待若干个其他线程执行完任务之后,它才执行,不可重复使用。
  • 方法:
    • void await():将门闩栓在此处,当前线程进入阻塞等待状态,只有门闩计数器为0时才会放行。
    • void countDown():门闩计数器减一。
    • long getCount():获取当前门闩数。
  • 应用场景:启动三个线程计算,需要对结果进行累加。

源码: /javase-advanced/

  • src: c.y.thread.communication.CountDownLatch
/**
 * @author yap
 */
public class CountDownLatchTest {

    @Test
    public void countDownLatch() {
        CountDownLatch countDownLatch = new CountDownLatch(8);

        new Thread(() -> {
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("competition is over...");
        }).start();

        for (int i = 0, j = 8; i < j; i++) {
            long sleepTime = i;
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(sleepTime);
                    System.out.println(Thread.currentThread().getName()
                            + ": reach and current count is "
                            + countDownLatch.getCount());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

6. CyclicBarrier

概念: CyclicBarrier表示一个循环的屏障,构造的时候需要设定一个最大值,每个线程在抵达屏障处的时候都会阻塞,当抵达的线程数达到屏障指定值时,屏障将被推倒,所有线程一起放行。

  • CyclicBarrier关注的是线程数,线程数达到指定值时放行。
  • CyclicBarrier可循环使用。
  • 构造:
    • CyclicBarrier(int parties, Runnable barrierAction)
      • param1: 指定屏障前阻塞线程的最大值,屏障前的线程数递增到这个值时放行。
      • param2: 当屏障被打破后执行的任务。
    • CyclicBarrier(int parties):不指定屏障被打破后执行的任务。
  • 方法:
    • void await():模拟一个线程达到了屏障处,阻塞等待。
    • int getNumberWaiting():返回屏障前的等待线程的数量。

源码: /javase-advanced/

  • src: c.y.thread.communication.CyclicBarrierTest
/**
 * @author yap
 */
public class CyclicBarrierTest {

    @Test
    public void cyclicBarrier() {

        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
            System.out.println("Enough for 3 people,start playing cards...");
        });

        for (int i = 0, j = 8; i < j; i++) {
            long sleepTime = i;
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(sleepTime);
                    System.out.println(Thread.currentThread().getName()
                            + ": ready..."
                            + cyclicBarrier.getNumberWaiting());
                    cyclicBarrier.await();
                } catch (BrokenBarrierException | InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": playing...");
            }).start();
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

7. Phaser

概念: Phaser表示一个分阶段的一次性屏障组,我们可以 Phaser 类前进方法来自定义阶段处理内容。

  • 重写前进方法: boolean onAdvance(int phase, int registeredParties)
    • 前进方法在每次进入一个新阶段的时候自动调用。
    • param1: 当前阶段标志,由整数表示,0表示第一阶段,以此类推。
    • param2: 晋级线程数,即当前阶段下仍处于注册状态的线程数。
    • return: false表示当前阶段结束,true表示所有阶段结束。
  • 构造:
    • Phaser():构建一个默认值为0的Phaser。
    • Phaser(int parties):构建一个指定数量的Phaser。
  • 方法:
    • int arriveAndAwaitAdvance():成功抵达当前阶段并且等待前进(进入下一阶段)。
    • int arriveAndDeregister():成功抵达当前阶段并且从屏障组中注销。
    • int bulkRegister(int parties):指定初始注册线程数,await线程数量达到parties值便触发前进方法,表示进入下一阶段。

源码: /javase-advanced/

  • src: c.y.thread.communication.PhaserTest
/**
 * @author yap
 */
public class PhaserTest {

    private static Phaser phaser;

    @Before
    public void before() {
        phaser = new Phaser() {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                final int levelOne = 1;
                final int levelTwo = 2;
                if (phase == 0) {
                    System.out.println(registeredParties + " hero ready...\n");
                    return false;
                }
                if (phase == levelOne) {
                    System.out.println(registeredParties + " hero passed level one...\n");
                    return false;
                }
                if (phase == levelTwo) {
                    System.out.println(registeredParties + " hero passed level two...\n");
                    return false;
                }
                System.out.println("over...");
                return true;
            }
        };
    }

    private static class Hero implements Runnable {

        private int heroLevel;

        private Hero(int heroLevel) {
            this.heroLevel = heroLevel;
        }

        @SneakyThrows
        private void ready() {
            TimeUnit.SECONDS.sleep(1L);
            System.out.println(Thread.currentThread().getName() + " ready...");
            phaser.arriveAndAwaitAdvance();
        }

        @SneakyThrows
        private void levelOne() {
            final int levelLimit = 2;
            if (heroLevel > levelLimit) {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName() + " passed level one...");
                phaser.arriveAndAwaitAdvance();
            }
        }

        @SneakyThrows
        private void levelTwo() {
            final int levelLimit = 4;
            if (heroLevel > levelLimit) {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName() + " passed level two...");
                phaser.arriveAndAwaitAdvance();
            }
            // Can only be written in the last level
            System.out.println(Thread.currentThread().getName() + " deregister...");
            phaser.arriveAndDeregister();
        }

        @Override
        public void run() {
            ready();
            levelOne();
            levelTwo();
        }
    }

    @Test
    public void phaser() {
        final int parties = 6;
        phaser.bulkRegister(parties);
        for (int i = 0; i < parties; i++) {
            new Thread(new Hero(i + 1), "hero-" + i).start();
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

8. Semaphore

概念: Semaphore表示信号量,可以控制最大并发数量,比如在构造的时候设置信号量为2,那么表示最多只允许两个线程并发运行,主要用于限流。

  • 构造:
    • Semaphore(int permits):构造一个指定初始信号数量的信号灯。
    • Semaphore(int permits, boolean fair):指定是公平还是非公平信号,默认非公平。
  • 方法:
    • void acquire():获取一个信号,获取不成功当前线程会阻塞等待。
    • void release():释放一个信号,此时其他线程可以重新获取这个信号。

源码: /javase-advanced/

  • src: c.y.thread.communication.SemaphoreTest
/**
 * @author yap
 */
public class SemaphoreTest {

    @Test
    public void semaphore() {
        Semaphore semaphore = new Semaphore(2);
        for (int i = 0, j = 10; i < j; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " start...");
                    TimeUnit.SECONDS.sleep(5L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }
}

9. Exchanger

概念: Exchanger用于交换数据,它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。

  • 构造:
    • Exchanger():Exchanger在构造的时候需要指定泛型,表示交换的数据的类型。
  • 方法:
    • V exchange(V x):交换数据,方法会阻塞,直到第二个线程调用 exchange()
    • param1: 发送出去的数据。
    • return: 接收回来的数据。
  • Exchanger需要作用于成对儿的线程,适用于两个线程之间的通信,如交易装备过程。

源码: /javase-advanced/

  • src: c.y.thread.communication.ExchangerTest
/**
 * @author yap
 */
public class ExchangerTest {

    @Test
    public void exchange() {
        Exchanger<String> exchanger = new Exchanger<>();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3L);
                System.out.println("Buyer ready...");
                String exchangedValue = exchanger.exchange("10 yuan");
                System.out.println("Buyer got " + exchangedValue);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println("Seller ready...");
                String exchangedValue = exchanger.exchange("bread");
                System.out.println("Seller got " + exchangedValue);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @SneakyThrows
    @After
    public void after() {
        System.out.println(System.in.read());
    }

}