java并发编程之线程之间通讯

本文代码示例已放入gitHub:请点击我

快速导航-------->src.main.java.yq.Thread.Communication

       在昨天我们在-- java并发编程之内存模型&多线程三大特性 -- 之中说到了线程安全问题就是因为线程之间通讯引起的,并且分析了为什么会产生线程安全问题。那么今天就来说说通讯。

什么是线程之间通讯?

        答:其实就是多个线程同时操作同一个资源,只是操作的动作不同而已。说白了就是线程一个生产,一个读取。

今天的目标:学会 wait notify ,Condition

接下来我们就使用代码实现使用多线程实现生产者和消费者的例子。

//测试多线程
@Data
public class MyThread {

    //volatile可以解决从排序,和线程可见性
    private volatile String userSex = null;
    private volatile String userName = null;
    //用于计数
    private volatile Integer number = 0;
//    //是否创建
//    private volatile Boolean isFalg = false;


    static class ProducerThread extends Thread {

        private MyThread myThread;

        public ProducerThread(MyThread myThread) {
            this.myThread = myThread;
        }

        @Override
        public void run() {
            int index = 0;
            while (true) {
                try {

                    Thread.sleep(50);
                    if (index % 2 == 0) {
                        myThread.setUserName("小红");
                        myThread.setUserSex("女");
                        myThread.setNumber(index);
                    } else {
                        myThread.setUserName("张三");
                        myThread.setUserSex("男");
                        myThread.setNumber(index);
                    }

                    index++;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

    static class ConsumerThread extends Thread {

        private MyThread myThread;

        public ConsumerThread(MyThread myThread) {
            this.myThread = myThread;
        }

        @Override
        public void run() {
            while (true) {
                try {

                    Thread.sleep(30);
                    System.out.println(Thread.currentThread().getName() + myThread.getUserName() + "--------" + myThread.getUserSex() + "---------" + myThread.getNumber());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new ProducerThread(myThread).start();
        new ConsumerThread(myThread).start();
    }
}

        通过上面的例子。我们创建了了一个消费者线程,和一个生产者线程,作用很简单,一个负责生产,一个负责消费,但是我们故意使用Thread.sleep()进行阻塞线程,而且生产者线程阻塞更久,用于模拟线程安全问题。

        我们从运行结果上看出来了,确实产生了线程安全问题,那这个时候怎么办呢?有人说使用lock锁或者使用synchronized关键字,解决线程安全问题。

        答案是不可以的,加锁使用场景是多个线程共享同一个变量的时候,一次只允许一个变量对功效变量进行操作,而我们这里只有一个生产者线程,也就是说根本就不会发生线程安全问题,拿有人会说,为什么我们这里发生了重复消费呢,这就是模拟的一个网络延迟的情况,确实生产者只生产了一个,但是在下次生产好之前,我们的消费者线程又去取了,所以才产生了重复读取,那么这种一对一,生产一个消费一个我们怎么解决呢?

1:使用wait,notify

//测试多线程
@Data
public class MyThread {

    //volatile可以解决从排序,和线程可见性
    private volatile String userSex = null;
    private volatile String userName = null;
    //用于计数
    private volatile Integer number = 0;
    //是否创建
    private volatile Boolean isFalg = false;


    static class ProducerThread extends Thread {

        private MyThread myThread;

        public ProducerThread(MyThread myThread) {
            this.myThread = myThread;
        }

        @Override
        public void run() {
            int index = 0;
            while (true) {
                try {
                    synchronized (myThread) {
                        //如果已经生产好了,还没人消费,那么我们这个线程就等待,先不进行生产
                        if(myThread.getIsFalg()){
                            myThread.wait();
                        }
                        Thread.sleep(50);
                        if (index % 2 == 0) {
                            myThread.setUserName("小红");
                            myThread.setUserSex("女");
                            myThread.setNumber(index);
                        } else {
                            myThread.setUserName("张三");
                            myThread.setUserSex("男");
                            myThread.setNumber(index);
                        }
                        index++;
                        //表示我们已经生产好了
                        myThread.setIsFalg(true);
                        //放开阻塞的线程
                        myThread.notify();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

    static class ConsumerThread extends Thread {

        private MyThread myThread;

        public ConsumerThread(MyThread myThread) {
            this.myThread = myThread;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    synchronized (myThread) {
                        //如果还没有生产好,我们就阻塞当前线程
                        if(! myThread.getIsFalg()){
                            myThread.wait();
                        }
                        Thread.sleep(30);
                        System.out.println(Thread.currentThread().getName() + myThread.getUserName() +
                                "--------" + myThread.getUserSex() + "---------" + myThread.getNumber());
                        myThread.setIsFalg(false);
                        myThread.notify();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        new ProducerThread(myThread).start();
        new ConsumerThread(myThread).start();
    }
}

        同样是上面的例子,我们稍加改造,增加了一个标识,flag--->这个标识标识的是否生产好了一个实例,同样我们加入了wait和notify关键字,以及synchronized关键字,我们看看运行效果。

        这个时候我们发现之前的重复读取的问题解决了,而且顺序很友好,一个女一个男,很现然已经达到了我们的预期效果,那么是为什么会达到这个效果呢?请看下面:

wati,notify,notifyAll
wati使前线程阻塞
notify放开当前阻塞的线程
notifyAll放开所有被阻塞的线程

        到了这里我们就应该明白为什么要那么写了,就是如果生产线程已经生产好了实例,但是没有被消费线程消费,那么我们就阻塞生产线程不让其生产,那么消费者线程也是同理,如果有已经被生产好的实例,我们就消费,如果还没有实例被生产好的实例,我们就阻塞消费线程另外,wait,notify必须结合synchronized来使用,而且要使用同一个锁对象,不然会抛出异常:IllegalMonitorStateException

        这里的wait是阻塞当前线程,那么我们之前还有个东西,Thread.sleep(毫秒单位),同样也是阻塞当前线程,那么二者的区别是什么?

区别:

  1. sleep方法虽然会阻塞线程,让出CPU资源给其他线程执行,但是其还是在监管状态,到了时间又会自动执行。
  2. sleep是属于Thread的一个方法,只能使用Thread进行调用。
  3. wait是属于Object类的,所有的对象都可以调用wait方法,也就是说仍和对象都可以作为所对象。
  4. wait不会自动执行,必须结合notify进行使用。

2:使用Condition实现生产者消费者功能

那么我们开始改造代码

//测试多线程
@Data
public class MyThread {

    //volatile可以解决从排序,和线程可见性
    private volatile String userSex = null;
    private volatile String userName = null;
    //用于计数
    private volatile Integer number = 0;
    //是否创建
    private volatile Boolean isFalg = false;
    private Lock lock = new ReentrantLock();


    static class ProducerThread extends Thread {

        private Condition condition;

        private MyThread myThread;

        public ProducerThread(MyThread myThread, Condition condition) {
            this.myThread = myThread;
            this.condition = condition;
        }

        @Override
        public void run() {
            int index = 0;
            while (true) {
                try {
                    myThread.getLock().lock();
                    //如果已经生产好了,还没人消费,那么我们这个线程就等待,先不进行生产
                    if (myThread.getIsFalg()) {
                        condition.await();
                    }
                    Thread.sleep(50);
                    if (index % 2 == 0) {
                        myThread.setUserName("小红");
                        myThread.setUserSex("女");
                        myThread.setNumber(index);
                    } else {
                        myThread.setUserName("张三");
                        myThread.setUserSex("男");
                        myThread.setNumber(index);
                    }
                    ++index;
                    //表示我们已经生产好了
                    myThread.setIsFalg(true);
                    //放开阻塞的线程
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    myThread.getLock().unlock();
                }
            }
        }

    }

    static class ConsumerThread extends Thread {

        private Condition condition;

        private MyThread myThread;

        public ConsumerThread(MyThread myThread, Condition condition) {
            this.myThread = myThread;
            this.condition = condition;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    myThread.getLock().lock();
                    //如果还没有生产好,我们就阻塞当前线程
                    if (!myThread.getIsFalg()) {
                        condition.await();
                    }
                    Thread.sleep(30);
                    System.out.println(Thread.currentThread().getName() + myThread.getUserName() +
                            "--------" + myThread.getUserSex() + "---------" + myThread.getNumber());
                    myThread.setIsFalg(false);
                    condition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    myThread.getLock().unlock();
                }
            }
        }
    }


    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Condition condition = myThread.lock.newCondition();
        new ProducerThread(myThread,condition).start();
        new ConsumerThread(myThread,condition).start();
    }
}

        大家可以同样看到,使用Conidtion也同样达到了synchronized配合wait和notify的功能,另外Conidtion是属于lock锁的。

        至于这样写的原因跟上面使用wait和notify的原理是一样的,就不过多解释为什么这样写了

 

Condition常用接口方法,红色为常用
名称作用
void await() throws InterruptedException;使线程进行阻塞
void awaitUninterruptibly();使线程进行阻塞,并且使用Thread.interrupt()不会报错
long awaitNanos(long nanosTimeout)  throws InterruptedException;阻塞线程,并且可以指定阻塞的时间,到时间就算没有使用signal或者signalAll也会重新执行,类型为long类型
boolean await(long time, TimeUnit unit) throws InterruptedException;同上,但是可以指定时间类型
boolean awaitUntil(Date deadline) throws InterruptedException;同上,但是时间类型是Date类型
void signal();唤醒被阻塞的线程
void signalAll();唤醒所有被阻塞的线程
线程中断常用方法
名称作用
interrupt()在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。
isInterrupted()用来判断当前线程的中断状态(true or false)。
interrupted()用来恢复中断状态

        如果想详细了解interrupt()请点击我:java中的interrupt使用

实现生产者消费者的两种方式:

  1. 使用 synchronized 加上wiat和notify实现。
  2. 使用 lock 配合Condition接口的await和signal实现。
  3. 两者的区别,就是Condition可以指定时间,到了时间就可以自动进行让线程进行自动执行。

到了这里我们基本就实现了生产者消费者的功能,也可以说是线程的通讯把,希望可以和大家共同进步,谢谢大家的阅读~~

本文代码示例已放入gitHub:请点击我

快速导航-------->src.main.java.yq.Thread.Communication

如果写的不对,还望大佬指出!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值