本文最后更新于 2023-09-19,文章内容可能已经过时。

3 线程间通信

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析

场景—两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

5.png

同时这里要补充一点,我们在进入线程通信的等待唤醒的条件判断,应该使用 while,这样能保证不会出现虚假唤醒,因为java的唤醒机制是,从哪里睡眠,就从哪里唤醒,如果使用if导致出现并不应该唤醒的情况下,线程被唤醒,错误的执行了不该执行的方法。

3.1 synchronized 方案

class share {
    // 资源
    private int numbers = 0;

    // +1 的方法
    public synchronized void incr() throws InterruptedException {
        // 第二步 判断 干活 通知
        while (numbers == 1) {
            this.wait();
        }
        // 如果 number 值为 0 就 + 1
        numbers++;
        this.notifyAll();
        System.out.println(Thread.currentThread().getName() + " :: " + numbers);
    }

    // -1 的方法
    public synchronized void decr() throws InterruptedException {
        while (numbers != 1) {
            this.wait();
        }
        numbers--;
        this.notifyAll();
        System.out.println(Thread.currentThread().getName() + " :: " + numbers);
    }
}

public class ThreadDemo1 {

    public static void main(String[] args) {
        share share = new share();

        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "AA").start();

        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "BB").start();
        
                new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "CC").start();

        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "DD").start();
    }
}

3.2 Lock 方案

切记使用 Lock 接口,一定要使用 try finally 环绕,防止出现异常之后导致死锁。

class Share {

    private int numbers = 0;
    private final Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    // + 1 方法
    public void incr() throws InterruptedException {

        try {
            lock.lock();
            while (numbers == 1){
                condition.await();
            }
            numbers ++;
            System.out.println(Thread.currentThread().getName() + " :: " + numbers);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    // - 1 方法
    public void decr() throws InterruptedException {
        try {
            lock.lock();
            while (numbers != 1){
                condition.await();
            }
            numbers --;
            System.out.println(Thread.currentThread().getName() + " :: " + numbers);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

}

public class ThreadDemo2 {

    public static void main(String[] args) {
        Share share = new Share();

        new Thread(()-> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "AA").start();

        new Thread(()-> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "BB").start();

        new Thread(()-> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.incr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "CC").start();

        new Thread(()-> {
            try {
                for (int i = 0; i < 10; i++) {
                    share.decr();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "DD").start();
    }
}

3.3 线程间定制化通信

3.3.1 案例介绍

问题: A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮

这里定义 flag 是为了线程阻塞的时候,能按情况进行阻塞,如果flag不到达合法值就一直等待被唤醒,而为什么设置三个condition,是因为唤醒的时候就不用使用 signalAll了,要不然还要全部唤醒,进入二次竞争,然后再次等待,每进行一次唤醒,就进行一次竞争行为,效率降低。

class ThreadShare {
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();
    private int flag = 0;

    public void print5(int loop) throws InterruptedException {
        try {
            lock.lock();
            while (flag != 0) {
                c1.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i + " 轮数 : " + loop + " flag : " + flag);
            }
            flag = (flag + 1) % 3;
            c2.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print10(int loop) throws InterruptedException {
        try {
            lock.lock();
            while (flag != 1) {
                c2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i + " 轮数 : " + loop);
            }
            flag = (flag + 1) % 3;
            c3.signal();
        } finally {
            lock.unlock();
        }
    }

    public void print15(int loop) throws InterruptedException {
        try {
            lock.lock();
            while (flag != 2) {
                c3.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + " :: " + i);
            }
            flag = (flag + 1) % 3;
            c1.signal();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadDemo3 {

    public static void main(String[] args) {
        ThreadShare share = new ThreadShare();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    share.print5(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "AA").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    share.print10(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "BB").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    share.print15(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "CC").start();
    }
}

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的class对象。
对于同步方法块,锁是synchonized括号里配置的对象