多线程间的通信

  • 需求
  • synchronized 实现
  • JUC Lock 实现
  • 多线程指定调用顺序


需求

    一般情况下,一个线程只负责自己的任务,执行完成之后就结束了,但是在多线程中,一个线程的处理有可能会依赖另一个线程的数据,所以是需要线程之间的通信,达到信息同步的目的。
    举个栗子:多个线程去操作初始值为0的一个变量,实现一个线程对该变量 +1 另一个线程对该变量 -1,交替打印。
    在多线程的编程中,参照:在高内聚低耦合的前提下,线程->操作->资源类的结构去做,这样逻辑清晰一点。在资源类中的代码编写,参照 判断 – 业务逻辑 – 通知的结构去做。

synchronized 实现

    代码实现 资源类

class Number {

    private int num = 0; // 定义一个变量

    public int getNum() {
        return num;
    }

    // +1 的方法
    public synchronized void increase() {
        while (num != 0) { // 使用while是为了防止线程的虚假唤醒
            try {
                this.wait(); // 线程等待
            } catch (InterruptedException e) { // 出现异常简单打印一下
                e.printStackTrace();
            }
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        this.notifyAll(); // 唤醒其它所有的线程
    }

    // -1 的方法
    public synchronized void decrease() {
        while (num == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        this.notifyAll();
    }

}

    调用.wait()方法:导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。
    调用.notify()方法:唤醒正在等待对象监视器的一个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的,并且由实施的判断发生。 线程通过调用wait方法之一等待对象的监视器。唤醒的线程将无法继续,直到当前线程放弃此对象上的锁定为止。 唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正在积极地竞争在该对象上进行同步; 例如,唤醒的线程在下一个锁定该对象的线程中没有可靠的权限或缺点。
    调用.notifyAll()方法:唤醒正在等待对象监视器的所有线程。 线程通过调用wait方法之一等待对象的监视器。

    测试代码

public static void main(String[] args) {
        Number num = new Number();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                num.increase();
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                num.decrease();
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                num.increase();
            }
        }, "C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                num.decrease();
            }
        }, "D").start();
    }
}

    测试结果

java server 线程通信 java线程之间的通信_java

JUC Lock 实现

    实现代码

class Number1 {

    // 锁
    private final transient Lock lock = new ReentrantLock();

    private final transient Condition condition = lock.newCondition();

    private int num = 0;

    public int getNum() {

        return num;

    }

    public void increase() {
        try {
            lock.lock();
            while (num != 0) {
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrease() {
        try {
            lock.lock(); // 加锁
            while (num == 0) {
                condition.await(); // 等待
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            condition.signalAll(); // 唤醒其它所有线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

    Lock :Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
    Condition:条件(也称为条件队列或条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait 。

多线程指定调用顺序

    多线程直接按照顺序调用 实现 线程A --> 线程B --> 线程C,启动三个线程要求如下,A打印5次 B打印10次 C打印15次 循环调用10遍,永远保证 A ->B ->C
    直接使用Lock实现

public class ThreadOrderAccess {

    public static void main(String[] args) {

        AccessResource resource = new AccessResource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.print5();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.print10();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.print15();
            }
        }, "C").start();

    }

}

// 资源类
class AccessResource {

    private final Lock lock = new ReentrantLock();

    private final Condition c1 = lock.newCondition();
    private final Condition c2 = lock.newCondition();
    private final Condition c3 = lock.newCondition();


    private int num = 1;

    public int getNum() {
        return this.num;
    }

    public void print5() {
        try {
            lock.lock();
            // 1、判断
            while (num != 1) {
                c1.await();
            }
            // 2、业务流程
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 3、通知
            num = 2; // 更换标志位
            c2.signal(); // 通知线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        try {
            lock.lock();
            // 1、判断
            while (num != 2) {
                c2.await();
            }
            // 2、业务流程
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 3、通知
            num = 3; // 更换标志位
            c3.signal(); // 通知线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15() {
        try {
            lock.lock();
            // 1、判断
            while (num != 3) {
                c3.await();
            }
            // 2、业务流程
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 3、通知
            num = 1; // 更换标志位
            c1.signal(); // 通知线程
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


}

    Condition的signal()方法可以指定唤醒某一个线程。