Day10 尚硅谷JUC——线程间通信

我是大白(●—●),这是我开始学习记录大白Java软件攻城狮晋升之路的第十天。 今天学习的是【尚硅谷】大厂必备技术之JUC并发编程

一、线程通信概述和案例分析

1. 多线程编程步骤(中部)

  1. 第一步:创建资源类,在资源类创建属性和操作方法
  2. 第二步:在资源类操作方法
    1. 判断
    2. 干活
    3. 通知
  3. 第三步:创建多个线程,调用资源类的操作方法
  4. 第四步:防止虚假唤醒问题。

2. 例子

有两个线程,实现对一个初始值是0的变量。一个线程对值+1,另外一个线程对值-1,达到交替执行的效果。
通过Object类的wait()和notify()或者notifyAll()方法实现
wait():在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
notify():唤醒在此对象监视器上等待的单个线程。
notifyAll(): 唤醒在此对象监视器上等待的所有线程。

3. Synchronized实现案例

package com.example.demo;

//第一步 创建资源类,定义属性和操作方法
class Share {

    private int number = 0;

    /**
     * 加一的方法
     * @throws InterruptedException
     */
    public synchronized void incr() throws InterruptedException {
        //第二步 判断 干活 通知
        if (number != 0) { //判断number的值是否为0,如果不是0,线程等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        this.notifyAll();
    }

    /**
     * 减一的方法
     * @throws InterruptedException
     */
    public synchronized void decr() throws InterruptedException {
        //判断
        if (number != 1) {
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        //通知其它线程
        this.notifyAll();
    }
}

public class ThreadDemo1 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

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

image.png

4. 虚假唤醒问题

当新增了两个线程进行加1减1操作后,代码如下所示:

package com.example.demo;

//第一步 创建资源类,定义属性和操作方法
class Share {

    private int number = 0;

    /**
     * 加一的方法
     * @throws InterruptedException
     */
    public synchronized void incr() throws InterruptedException {
        //第二步 判断 干活 通知
        if (number != 0) { //判断number的值是否为0,如果不是0,线程等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        this.notifyAll();
    }

    /**
     * 减一的方法
     * @throws InterruptedException
     */
    public synchronized void decr() throws InterruptedException {
        //判断
        if (number != 1) {
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName() + "::" + number);
        //通知其它线程
        this.notifyAll();
    }
}

public class ThreadDemo1 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();

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

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

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

最终的结果将会是这样:
image.png
这就是虚假唤醒的情况,当某一时刻C线程进行+1操作后调用了notifyAll()方法,ABD线程均被唤醒且不需要进行判断,因此A线程又执行了+1操作,导致number的值变为了2的情况。
当一定的条件触发时会唤醒很多在阻塞态的线程,但只有部分的线程唤醒是有用的,其余线程的唤醒是多余的。

5. 解决虚假唤醒问题

官方jdk中推荐的一种方法用来解决虚假唤醒的方法,即将if改为while循环(具体实例可看下一节Lock实现案例代码):

synchronized (obj) { 
    while (<condition does not hold>) 
    { 
    	obj.wait(timeoutMillis, nanos); 
	} ... // Perform action appropriate to condition or timeout 
}  

6. Lock实现案例

使用的主要是Condition接口中的await()和singal()或者singalAll()方法进行通信
await():造成当前线程在接到信号或被中断之前一直处于等待状态。
singal():唤醒一个等待线程。
singalAll():唤醒所有等待线程。

package com.example.demo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//第一步 创建资源类,定义属性和操作方法
class LShare {

    private int number = 0;

    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    /**
     * 加一的方法
     * @throws InterruptedException
     */
    public void incr() throws InterruptedException {
        lock.lock();
        try{
            //第二步 判断 干活 通知
            while (number != 0) { //判断number的值是否为0,如果不是0,线程等待
                condition.await();;
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    /**
     * 减一的方法
     * @throws InterruptedException
     */
    public synchronized void decr() throws InterruptedException {
        lock.lock();
        try{
            //判断
            while (number != 1) {
                condition.await();
            }
            //干活
            number--;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            //通知其它线程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}
public class ThreadDemo2 {
    //第三步 创建多个线程,调用资源类的操作方法
    public static void main(String[] args) {
        LShare share = new LShare();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "AA").start();

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

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

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

二、线程间定制化通信

image.png
image.png

package com.example.demo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
 * AA打印5次,BB打印10次,CC打印15次
 * 紧接着
 * AA打印5次,BB打印10次,CC打印15次
 * 。。。。
 * 来10轮
 */
class ShareResource {
    //定义标志位
    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();

    //打印5次,参数第几轮
    public void print5(int loop) {
        lock.lock();
        try {
            //1.判断
            while (flag != 1) {
                c1.await(); //等待
            }
            //2.干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i + ",轮数:" + loop);
            }
            //修改标志位
            flag = 2;
            //3.通知BB线程
            c2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10(int loop) {
        lock.lock();
        try {
            while (flag != 2) {
                c2.await();
            }
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i + ",轮数:" + loop);
            }
            flag = 3;
            c3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15(int loop) {
        lock.lock();
        try {
            while (flag != 3) {
                c3.await();
            }
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i + ",轮数:" + loop);
            }
            flag = 1;
            c1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print5(i);
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print10(i);
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print15(i);
            }
        }, "CC").start();
    }
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值