线程间通信

2022年01月30日8 min read
并发编程

线程间通信的几种实现方式

题目:有多个个线程分别对int number操作,一共操作十次,当number等于0时number加一,当number等于1时number减一

方式一:使用Object类的wait() 和 notifyAll() 方法

class Share {  
    /**  
     * 初始值  
     */  
    private int number = 0;  
  
    /**  
     * +1的方法  
     */  
    public synchronized void incr() throws InterruptedException {  
        // 第二部 判断 干活 通知  
        // 判断number值是否是0,如果不是0,等待  
        if (number != 0) {  
            this.wait();  
        }  
        // 弱国number值是0,就+1操作  
        number++;  
        System.out.println(Thread.currentThread().getName() + " :: " + number);  
        // 通知其他线程  
        this.notifyAll();  
    }  
  
    /**  
     * 减一的方法  
     */  
    public synchronized void decr() throws InterruptedException {  
        // 判断  
        if (number != 1) {  
            this.wait();  
        }  
        // 干活  
        number--;  
        System.out.println(Thread.currentThread().getName() + " :: " + number);  
        this.notifyAll();  
    }  
}  
  
/**  
 * @author myAnswer <2273024587@qq.com>  
 * @since 2022/1/30 20:12  
 */  
public class ThreadDemo1 {  
    public static void main(String[] args) {  
        Share share = new Share();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.incr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "AA").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.decr();   
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "BB").start();  
    }  
}

输出:

虚假唤醒问题

这里我们创建4个线程发别执行加减的方法

public class ThreadDemo1 {  
    public static void main(String[] args) {  
        Share share = new Share();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.incr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "AA").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.decr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "BB").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.incr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "CC").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.decr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "DD").start();  
    }  
}

可以看到结果和预期不一致

解决方法:使用while判断,线程被唤醒时还要经过判断才往下走

public synchronized void incr() throws InterruptedException {  
    // 第二部 判断 干活 通知  
    // 判断number值是否是0,如果不是0,等待  
    while (number != 0) {  
        this.wait();  
    }  
    // 如果number值是0,就+1操作  
    number++;  
    System.out.println(Thread.currentThread().getName() + " :: " + number);  
    // 通知其他线程  
    this.notifyAll();  
}

方式二:使用 ReentrantLock

class Share {  
    private int number = 0;  
  
    /**  
     * 创建Lock  
     */  
    private Lock lock = new ReentrantLock();  
    private Condition condition = lock.newCondition();  
  
    /**  
     * +1  
     */  
    public void incr() throws InterruptedException {  
        // 上锁  
        lock.lock();  
        try {  
            // 判断  
            while (number != 0) {  
                condition.await();  
            }  
            // 干活  
            number++;  
            System.out.println(Thread.currentThread().getName() + " :: " + number);  
            // 通知  
            condition.signalAll();  
        } finally {  
            // 解锁  
            lock.unlock();  
        }  
    }  
  
    /**  
     * 1-  
     */  
    public void decr() throws InterruptedException {  
        // 上锁  
        lock.lock();  
        try {  
            // 判断  
            while (number != 1) {  
                condition.await();  
            }  
            // 干活  
            number--;  
            System.out.println(Thread.currentThread().getName() + " :: " + number);  
            // 通知  
            condition.signalAll();  
        } finally {  
            // 解锁  
            lock.unlock();  
        }  
    }  
}  
  
/**  
 * @author myAnswer <2273024587@qq.com>  
 * @since 2022/1/30 20:55  
 */  
public class ThreadDemo2 {  
    public static void main(String[] args) {  
        Share share = new Share();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.incr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "AA").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.decr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "BB").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.incr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "CC").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    share.decr();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "DD").start();  
    }  
}

运行结果为:

线程间定制化通信

synchronized实现线程间通信的案例如果有多个线程那么就无法指定线程执行的顺序,我们可以使用Lock锁的Condition 实现线程定制化通信

实例: A线程打印2次A,B线程打印3次B,C线程打印5次C,按照此顺序循环10轮

思路:创建三个线程给每个线程设置一个标志位

线程A判断flag=0,打印2次,修改flag=1,通知线程B

线程B判断flag=1,打印3次,修改flag=2,通知线程C

线程C判断flag=2,打印5次,修改flag=0,通知线程A

/**  
 * 第一步 创建资源类  
 */  
class ShareResource {  
    /**  
     * 定义标志位  
     * 1 AA  
     * 2 BB  
     * 3 CC  
     */  
    private int flag = 1;  
    /**  
     * 创建Lock锁  
     */  
    private Lock lock = new ReentrantLock();  
    /**  
     * 创建三个condition  
     */  
    private Condition c1 = lock.newCondition();  
    private Condition c2 = lock.newCondition();  
    private Condition c3 = lock.newCondition();  
  
    /**  
     * @param loop 轮数  
     * @throws InterruptedException  
     */  
    public void print5(int loop) throws InterruptedException {  
        // 上锁  
        lock.lock();  
        try {  
            // 判断  
            while (flag != 1) {  
                // 等待  
                c1.await();  
            }  
            // 干活  
            for (int i = 1; i <= 5; i++) {  
                System.out.println(Thread.currentThread().getName() + " :: " + i + " :轮数" + loop);  
            }  
            // 通知  
            // 修改标志位2  
            flag = 2;  
            // 通知BB线程  
            c2.signal();  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    /**  
     * @param loop 轮数  
     * @throws InterruptedException  
     */  
    public void print10(int loop) throws InterruptedException {  
        // 上锁  
        lock.lock();  
        try {  
            // 判断  
            while (flag != 2) {  
                // 等待  
                c2.await();  
            }  
            // 干活  
            for (int i = 1; i <= 10; i++) {  
                System.out.println(Thread.currentThread().getName() + " :: " + i + " :轮数" + loop);  
            }  
            // 通知  
            // 修改标志位2  
            flag = 3;  
            // 通知BB线程  
            c3.signal();  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    /**  
     * @param loop 轮数  
     * @throws InterruptedException  
     */  
    public void print15(int loop) throws InterruptedException {  
        // 上锁  
        lock.lock();  
        try {  
            // 判断  
            while (flag != 3) {  
                // 等待  
                c3.await();  
            }  
            // 干活  
            for (int i = 1; i <= 15; i++) {  
                System.out.println(Thread.currentThread().getName() + " :: " + i + " :轮数" + loop);  
            }  
            // 通知  
            // 修改标志位2  
            flag = 1;  
            // 通知BB线程  
            c1.signal();  
        } finally {  
            lock.unlock();  
        }  
    }  
}  
  
/**  
 * @author myAnswer <2273024587@qq.com>  
 * @since 2022/1/30 21:50  
 */  
public class ThreadDemo3 {  
    public static void main(String[] args) {  
        ShareResource shareResource = new ShareResource();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    shareResource.print5(i);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "AA").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 10; i++) {  
                try {  
                    shareResource.print10(i);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "BB").start();  
        new Thread(() -> {  
            for (int i = 1; i <= 15; i++) {  
                try {  
                    shareResource.print15(i);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        }, "CC").start();  
    }  
}

输出结果为: