Java线程间通信方式

这篇具有很好参考价值的文章主要介绍了Java线程间通信方式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前文了解了线程的创建方式和状态切换,在实际开发时,一个进程中往往有很多个线程,大多数线程之间往往不是绝对独立的,比如说我们需要将A和B 两个线程的执行结果收集在一起然后显示在界面上,又或者比较典型的消费者-生产者模式,在这些场景下,线程间通信成了我们必须使用的手段,那么线程之间怎么通信呢?

线程间通信方式,从实现本质来讲,主要可以分为两大类共享内存和消息传递。

相信大家还记得,在内存模型一节,我们提到多线程并发情况下的三大特性,原子性,有序性,可见性,其所对应的解决方案就可以用来实现线程间通信,这些解决方案的本质就是共享内存。

对于消息传递而言,最经典的实现就是我们的Handler机制,在子线程使用主线程的Handler对象将一些信息发送到主线程以便进行处理。

下面我们来看一些线程间通信的典型实现

Object.wait/Object.notify

对于Object对象而言,其提供了等待/通知机制以便实现线程间通信,由于Object是Java中所有类的父类,也就意味着Java中所有对象都支持通知/等待机制,与该机制关联的主要有五个方法:

方法名称 描述 备注
wait() 线程执行中调用对象的wait方法可以使得当前线程进入WAITING状态,只有等待其他线程的通知或被中断才会返回,需要注意的是,调用wait方法后,会释放对象的锁 /
wait(long timeout) 与wait含义一致,不同的是通过timeout指定了超时时间,如果时间到了还没收到通知就超时返回 /
wait(long timeout, int nanos) 超时管控更加精确,第二个参数单位为毫微秒 /
notify 通知一个在对象上等待的线程使其从wait对象返回 /
notifyAll 通知所有等待在该对象上的线程 /

以Object.wait/Object.notify实现一个典型的消息者生产者模型,消费者对变量做-1操作,生产者对变量做+1操作,代码如下:

// 盘子
public class Number {
    // 盘子当前容量,是否有内容
    private int mCount = 0;
  
    //对盘子容量进行+1操作
    public void inc() {
        if (mCount != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mCount++;
        System.out.println(Thread.currentThread().getName()+",mCount+1,mCount="+mCount);
        this.notifyAll();
    }

    //对盘子容量进行-1操作
    public void dec() {
        if (mCount == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mCount--;
        System.out.println(Thread.currentThread().getName()+",mCount-1,mCount="+mCount);
        this.notifyAll();
    }
}
    public static void main(String[] args) {
        Number number = new Number();
        // 生产者线程
        Thread incThread = new Thread(new Runnable() {
            @Override
            public void run() {
                number.inc();
            }
        });
        incThread.setName("Inc Thread");
        incThread.start();

        // 消费者线程
        Thread decThread = new Thread(new Runnable() {
            @Override
            public void run() {
                number.dec();
            }
        });
        decThread.setName("Dec Thread");
        decThread.start();
    }

如上述代码备注,其中Inc Thread为生产者线程,当盘子内容为0时,每次向盘子Number中放一个内容,消费者线程Dec Thread当盘子有内容时,消耗内容,让盘子内容变为0.运行输出如下:

java线程通信,java,jvm,开发语言,android

糟糕,正确运行一个循环后,抛出了IllegalMonitorStateException,为什么会这样呢?这个异常是什么意思?

遇事不决看源码,IllegalMonitorStateException的类说明如下:

Thrown to indicate that a thread has attempted to wait on an object’s monitor or to notify other threads waiting on an object’s monitor without owning the specified monitor.

翻译过来的意思就是当线程在没有持有特定的锁的情况下试图等待对象锁或者通知其他线程等待对象锁会抛出此异常,有点拗口,先放置,即然我们调用了wait/notifyAll这两个方法,不妨看下这两个方法的说明,看是否有新的提示,wait方法说明如下:

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 * <p>
 * The current thread must own this object's monitor. The thread
 * releases ownership of this monitor and waits until another thread
 * notifies threads waiting on this object's monitor to wake up
 * either through a call to the {@code notify} method or the
 * {@code notifyAll} method. The thread then waits until it can
 * re-obtain ownership of the monitor and resumes execution.
 * <p>
 * As in the one argument version, interrupts and spurious wakeups are
 * possible, and this method should always be used in a loop:
 * <pre>
 *     synchronized (obj) {
 *         while (&lt;condition does not hold&gt;)
 *             obj.wait();
 *         ... // Perform action appropriate to condition
 *     }
 * </pre>
 * This method should only be called by a thread that is the owner
 * of this object's monitor. See the {@code notify} method for a
 * description of the ways in which a thread can become the owner of
 * a monitor.
 *
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of the object's monitor.
 * @throws  InterruptedException if any thread interrupted the
 *             current thread before or while the current thread
 *             was waiting for a notification.  The <i>interrupted
 *             status</i> of the current thread is cleared when
 *             this exception is thrown.
 * @see        java.lang.Object#notify()
 * @see        java.lang.Object#notifyAll()
 */
public final void wait() throws InterruptedException {
    wait(0);
}

在上述说明中反复提到 The current thread must own this object’s monitor. This method should only be called by a thread that is the owner of this object’s monitor.也就是说在调用Object.wait方法前,当前线程必须持有该对象的锁,获取锁的方法很简单,wait方法说明中也有,通过synchronized关键词,那么正确的调用代码如下所示:

public class Number {
    private int mCount = 0;
    public void inc() {
        synchronized (this) {
            if (mCount != 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            mCount++;
            System.out.println(Thread.currentThread().getName()+",mCount+1,mCount="+mCount);
            this.notifyAll();
        }
    }

    public void dec() {
        synchronized (this) {
            if (mCount == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            mCount--;
            System.out.println(Thread.currentThread().getName()+",mCount-1,mCount="+mCount);
            this.notifyAll();
        }
    }
}

重新运行代码,输出如下:

java线程通信,java,jvm,开发语言,android

这里可以看出,只运行了一个循环,那么怎么让它一直运行呢?将if修改称while即可,以生产10次为例,如需一直生产消息,使用while(true)即可,代码及输出如下:

public class Number {
    private int mCount = 0;
    private int mIncTimes = 0;
    private int mDecTimes = 0;
    public void inc() {
        synchronized (this) {
            while (mIncTimes < 10) {
                if (mCount != 0) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mCount++;
                mIncTimes ++;
                System.out.println(Thread.currentThread().getName()+",mCount+1,mCount="+mCount+",mIncTimes:"+mIncTimes);
                this.notifyAll();
            }
        }
    }

    public void dec() {
        synchronized (this) {
            while (mDecTimes < 10) {
                if (mCount == 0) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mCount--;
                mDecTimes++;
                System.out.println(Thread.currentThread().getName()+",mCount-1,mCount="+mCount+",mDecTimes:"+mDecTimes);
                this.notifyAll();
            }
        }
    }
}

java线程通信,java,jvm,开发语言,android

综上,使用Object.wait/Object.notify/Object.notifyAll时,切记其必须先使用关键词获取同个Object的对象锁,否则就会抛出IllegalMonitorStateException异常

Semaphore

Semaphore翻译为信号量,一个信号量维护一组许可,在调用acquire方法时阻塞,直到获取许可,在调用release的时候释放占用,从而唤醒阻塞的某个线程。信号量操作类似于停车场车辆管理,初始时停车场有5个车位,当停车场内部5个车位全占满时,此时可用资源为0,即信号量可用许可数量为0,其他车辆想停车就只能在停车场外排队阻塞(相当于调用acquire),当一辆车辆从停车场驶出时(相当于调用release方法),此时信号量许可数量为1,唤醒一个等待停车的车辆进入停车辆,自身可用许可数量再次为0,依此往复。

对于只有一个许可的信号量而言,其可用许可数量为0或1,故被称为二进制信号量,对于有多个正整数可用许可数据的信号量而言,其被称为通用信号量。需要注意在执行acquire时信号量本身并不会持有同步锁,因为这样会影响被释放的许可进入可用许可池中。

二进制信号量,不同于其他锁机制,要求释放锁的线程和获取锁的线程是同一个,也就意味着我们可以在其他线程释放二进制信号量以完成死锁恢复。

下面我们以二进制信号量实现消费者生产者模式,代码如下(生产消费4次即停止):

public class Counter {
    private int mCount = 0;
    public void incCount() {
        mCount ++;
    }

    public void decCount() {
        mCount--;
    }

    public int getCount() {
        return mCount;
    }
}

// Main主类代码
private static int mIncTimes = 0;
public static void main(String[] args) {
    Counter counter = new Counter();
    Semaphore semaphore = new Semaphore(1);
    Thread incThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (mIncTimes < 4) {
                try {
                    semaphore.acquire();
                    if (counter.getCount() == 0) {
                        counter.incCount();
                        mIncTimes ++;
                        System.out.println("Inc Thread ++,current count is:" + counter.getCount());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
        }
    });
    incThread.setName("Inc Thread");
    incThread.start();

    Thread decThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (mIncTimes < 4) {
                try {
                    semaphore.acquire();
                    if (counter.getCount() != 0) {
                        counter.decCount();
                        System.out.println("Dec Thread --,current count is:" + counter.getCount());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
        }
    });
    decThread.setName("Dec Thread");
    decThread.start();
}

运行结果如下:

java线程通信,java,jvm,开发语言,android

内存一致性影响,要求一个线程中的release操作和另一个线程中的acquire操作必须存在happen-before关系文章来源地址https://www.toymoban.com/news/detail-729113.html

到了这里,关于Java线程间通信方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java死锁、线程状态、线程通信、线程池

    java实现多线程: [1]继承Thread类并重写run方法 [2]实现Runnable接口 线程Thread中常用的方法: setName(): Thread.currentThread().getName(): ​ static void sleep(); static void yield(): join(): setDeamon()设置后台线程 线程安全问题: ---当多个线程共享同一个资源时,对该资源的操作就会出现线程安全问题。

    2024年02月13日
    浏览(30)
  • [Java]线程生命周期与线程通信

    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://www.cnblogs.com/cnb-yuchen/p/18162522 出自【进步*于辰的博客】 线程生命周期与进程有诸多相似,所以我们很容易将两者关联理解并混淆,一些细节之处确有许多不同,因为线程调度与进程调度虽都由

    2024年04月27日
    浏览(24)
  • Java 进阶(12) 线程通信

    多个线程在处理同⼀个资源,但是处理的动作(线程的任务)却不相同。 为什么要处理线程间通信 多个线程并发执⾏时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成⼀件任务,并且我们希望他们有规律的执⾏, 那么多线程之间需要⼀些协调通信,以此

    2023年04月16日
    浏览(28)
  • 【Java】详解多线程通信

    🌺 个人主页: Dawn黎明开始 🎀 系列专栏: Java ⭐ 每日一句:什么都不做,才会来不及 📢 欢迎大家:关注 🔍+ 点赞 👍+评论 📝+收藏⭐️ 文章目录 🔐多线程通信 (1).🔓由来 (2).🔓成员方法  (3).🔓案例引入 (4).🔓代码实现       现代社会崇尚合作精神,分工合作在日常

    2024年02月05日
    浏览(29)
  • Java多线程 - Java创建线程的4种方式

    1. Java创建线程有哪几种方式? 一个线程在Java中使用一个Thread实例来描述。Thread类是Java语言的一个重要的基础类,位于java.lang包中。Thread类有不少非常重要的属性和方法,用于存储和操作线程的描述信息。 Thread类的构造方法: 1.1 线程创建方法一:继承Thread类创建线程类 (

    2023年04月08日
    浏览(27)
  • 深入理解Java线程间通信

    合理的使用Java多线程可以更好地利用服务器资源。一般来讲,线程内部有自己私有的线程上下文,互不干扰。但是当我们需要多个线程之间相互协作的时候,就需要我们掌握Java线程的通信方式。本文将介绍Java线程之间的几种通信原理。 在Java中,锁的概念都是基于对象的,

    2024年02月09日
    浏览(34)
  • java 线程安全问题 三种线程同步方案 线程通信(了解)

    线程安全问题指的是,多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。 下面代码演示上述问题,先定义一个共享的账户类: 在定义一个取钱的线程类 最后,再写一个测试类,在测试类中创建两个线程对象 某个执行结果: 为了解决前面的线程安全问题,

    2024年02月09日
    浏览(33)
  • Java——》线程间是如何通信的

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月09日
    浏览(30)
  • java语法(二)线程并发、Juit单元测试、反射机制、注解、动态代理、XML解析、JVM

    正则表达式验证网站 1、 ? :表示前边这个字符可以出现0次或者1次。例如下边 /used? 既可以匹配 use 也可以匹配 used 。 2、 * :匹配0个或者多个字符, * 号代表前边这个字符可以出现0次或者多次。例如 /ab*c 可以匹配 ac、abc、abbbbc 3、 + :与 * 号不同的是, + 需要前面这个字符

    2024年02月06日
    浏览(33)
  • Android系统-线程-java线程

    Android的框架应用是java环境的。java天生就是多线程。所以对java线程的理解尤为重要。 NEW 初始状态 RUNNABLE 运行状态 BLOCKED 阻塞状态 WAITING 等待状态 TIME_WAITING 超时等待状态 TERMINATED 终止状态 注意:调用obj.wait()的线程需要先获取obj的monitor,wait()会释放obj的monitor并进入等待态。

    2024年02月11日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包