设为首页 加入收藏

TOP

我看谁还不懂多线程之间的通信+基础入门+实战教程+详细介绍+附源码(一)
2023-07-25 21:30:51 】 浏览:57
Tags:程之间 通信

一、多线程之间的通信(Java版本)

1、多线程概念介绍

多线程概念

  • 在我们的程序层面来说,多线程通常是在每个进程中执行的,相应的附和我们常说的线程与进程之间的关系。线程与进程的关系:线程可以说是进程的儿子,一个进程可以有多个线程。但是对于线程来说,只属于一个进程。再说说进程,每个进程的有一个主线程作为入口,也有自己的唯一标识PID,它的PID也就是这个主线程的线程ID

  • 对于我们的计算机硬件来说,线程是进程中的一部分,也是进程的的实际运作单位,它也是操作系统中的最小运算调度单位。多线程可以提高CPU的处理速度。当然除了单核CPU,因为单核心CPU同一时间只能处理一个线程。在多线程环境下,对于单核CP来说,并不能提高响应速度,而且还会因为频繁切换线程上下文导致性能降低。多核心CPU具有同时并行执行线程的能力,因此我们需要注意使用环境。线程数超出核心数时也会引起线程切换,并且操作系统对我们线程切换是随机的。

2、线程之间如何通信

引入

  • 对于我们Java语言来说,多线程编程也是它的特性之一。我们需要利用多线程操作同一共享资源,从而实现一些特殊任务。上面说了,多线程在进行切换时CPU随机调度的,假如我们直接运行多个线程操作共享资源的话,势必会引起一些不可控错误因素。
  • 接下来,我们就需要让这些不可控变为可控 !这个时候就引出了本文的重点线程通信。线程通信就是为了解决多线程对同一共享变量的争夺

Java 线程通信的方式

  • 共享内存机制
    • 比如说Java的volatile关键字就是基于内存屏障解决变量的可见性,从而实现其他线程访问共享变量都是必须从主存中获取(对应其他线程对变量的更新也得及时的刷新到主存)。
    • synchronized 关键字基于对象锁这种方式实现线程互斥,可以通知对方有其他的线程正在执行这部分代码。
  • 消息传递模式
    • wait() 和 notify()/notifyAll() 等待通知方式实现线程的阻塞就绪状态之间的转换。
    • park、unpark
    • join() 阻塞【底层也是依赖wait实现】。
    • interrupt()打断阻塞状态。
    • 管道输入/输出。

3、线程通信方法详细介绍

主要介绍wait/notify,也有ReentrantLock的Condition条件变量的await/signal,LockSupport的park/unpark方法,也能实现线程之间的通信。主要是阻塞/唤醒通信模式。

首先说明这种方法一般都是作用于调用方法的所在线程。比如在主线程执行wait方法,就是将主线程阻塞了。

wait/notify机制

  • wait()、notify方法在Java中是Object提供给我们的。又因为所有的类都默认隐式继承了Object类,进而我们的每一个对象都具有wait和notify。
    • wait方法含义:一个线程一旦调用了任意对象obj.wait()方法,它就释放了所持有的监视器对象(obj)上的锁,并转为非运行状态(阻塞)。
    • notify方法含义:一个线程若执行obj.notify方法,则随机唤醒obj对象上监视器(操作系统也称为管程)monitor的阻塞队列waitset中一个线程。
    • wait和notify方法的使用同时必须配合synchronized关键字使用。同时也需要成对出现。就是说wait和notify必须得在同步代码块内部使用,大致原因就是需要保证同时只有一个线程可以去执行wait,使该线程阻塞。

await/signal

  • 要想使用await/signal首先是需要借用Condition条件变量,要想获取Condition条件变量,就必须通过ReentrantLock锁获取。
  • ReentrantLock和Synchronized类似,都是可重入锁,并且大多都是当做重量级锁使用。
    • 区别:ReentrantLock是API层面实现的,我们可以根据自己随意调用定制,但是Synchronized是JVM底层实现,我们无需关心他上锁解锁的流程。
  • await/signal使用时需要配合ReentrantLock锁对象的lock和unlock方法加锁解锁。就像wait/notify在synchronized在同步代码块中使用一样。他们都需要保证当前线程是唯一执行这段逻辑的线程。防止出现多线程造成的线程安全问题。

park/unpark

二、线程通信过程中需要注意的问题

1、唤醒丢失

如果一个线程先于被通知线程调用wait()前调用了notify(),等待的线程将错过这个信号。

  • 唤醒丢失主要是在我们使用wait 和 notify的过程中的时序问题。比如说我们线程二在执行某个对象notify的时候,线程一还没有执行该对象的wait方法。那么这次的唤醒就会丢失,我们就不能让线程二得notify方法起作用,自然而然线程一就不会被唤醒。
  • 举个例子吧,这就好比我们平常在宿舍每天都会有叫醒服务,但是这次 因为一些原因(通宵···)我一整晚都没有睡觉,而且当第二天早上的叫醒服务来的 时候也是醒着的。那么叫醒服务就会以为你已经醒来了,就会视而不见。没想到吧,叫醒服务刚走我就躺下来睡着了,所以我错过了这次叫醒服务。就能好好的睡亿觉了。这看起来没有什么大问题,但是你仔细想想若是每个睡着的人都需要被叫醒服务才能醒过来,外加上只有一次叫醒服务的机会。那么你就可以沉睡万年了,开心不。
  • 哈哈哈···
  • 这在程序中也是一样 的,如果错过notify那么就会一直wait。
    • 所以我们必须预防这种问题,比如说每隔一段时间去唤醒,也就是隔两分钟就去叫醒睡着的人。但是这种缺点就是太累了,对于程序来说是消耗性能和内存。实现也简单就是写入while循环体中,不停地尝试即可。
    • 我们也可以使用一个标志位完美的实现。初始化设置flag=FALSE表示还没wait,在wait之前将设置flag=TRUE,在notify之后设置flag=FALSE。每次notify唤醒之前都判断flag=true是否已经wait,在wait中判断flag=false是否已经notify。

核心代码演示

  • 首先使用线程池创建线程一使自己进入阻塞态,然后再调用LOCK1的notify方法唤醒线程一
	    // 线程一使用LOCK1对象调用wait方法阻塞自己
        executor.execute(new ThreadTest("线程一",LOCK1,LOCK2));

        synchronized (LOCK1) {
            System.out.println("main执行notify方法让线程一醒过来");
            LOCK1.notify();
        }
  • 但是他很有可能醒不来,因为主线程调用LOCK1对象的notify方法,可能主线程已经执行完了,上面线程还没创建完成,也就是没有进入wait状态。就醒不来了。

  • 解决方式:使用信号量标志进行判断是否已经进入wait

            synchronized (LOCK1) {
                while (true) {
                    if (FLAG.getFlag()) {
                        System.out.println("m
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Junit启动测试mybatis xml文件Bin.. 下一篇学习笔记——base标签、加密方式..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目