时间:2024-07-28
魏 瑾
(山西机电职业技术学院,山西 长治 046011)
Java的多线程技术在实际应用过程中必须解决好各个线程之间的通信问题,因为缺乏有效的通信机制,会造成Java多线程无法合理调度,进而增加系统崩溃的可能性。研究共享内存、消息通知等在多线程通信中的原理和应用具有重要的意义。
Java在软件开发中占据着非常广泛的市场,目前是最主流的开发语言,其优点在于执行效率高(相对于Python、C#、PHP等),其中多线程是Java的显著优势之一。计算机中的程序在运行时会产生一个独立的进程,并占据一定的计算机资源,CPU、内存等都会分配给进程。在Java语言下,进程可存在多个线程,这些线程并没有相互独立的存储空间,而是采用共享机制。多线程的价值在于能够分开执行不同的任务或者分段执行程序代码,多线程的应用可显著提高程序的运行效率、提高其负载高并发的能力以及更加充分的利用计算机资源,如CPU的利用率会因为多线程的应用而更高。但是从多线程的运行情况来看,当其在同步执行不同的任务时难免会出现资源争夺的问题,这种情况下就要借助线程间通信来合理地调度计算资源,保证每一个线程的顺利执行。Java多线程在执行层面采用交替进行或者接力进行,但无论如何实现,都会用到线程间通信[1]。
在网络通信设计中必须考虑到效率问题和编码的难度,网络用户的规模非常庞大,同一时间段之内可能具有大量的并行的网络应用,这种情况对服务器的负载能力提出了很高的要求,Java的多线程是提高服务器利用率以及应对高并发的重要技术措施,其应用价值很大,这也是高性能网站大多利用Java来编程的原因之一。
各个并发执行的线程之间存在争夺CPU和存储空间的情况,如果相互之间没有统一的协调机制,那么程序执行必然会受到严重影响,甚至阻塞,这是开展多线程网络通信的主要原因。以下内容介绍Java多线程通信的实现原理。
1.2.1 线程间通信的控制机制
1) 临界区。这种方式主要用于线程访问共享资源,其特点是速度快,实现方式为多线程的串行化;2) 互斥量。当两个线程为互斥的对象时,其资源空间可共享,其他的线程不能占用其共享空间,这样可保证其他线程不访问其公共资源;3) 信号量。Java的多线程并不能保证同一时刻所有的线程都能获得运行所需的资源,毕竟CPU和存储空间的性能是有限的,在确保运行效率的基本前提下尽可能多地运行线程,这才是比较科学的策略。信号量的作用是限制同一时刻访问资源的最大线程数量,显然,在信号量的控制下,只有有限数量线程可运行[2];4) 信号。可用于判断线程的优先级,决定了哪些线程先执行,哪些线程后执行。
1.2.2 线程通信方式
1) 共享内存。这是Java多线程通信最典型的方式。其原理是在设置一个可由多个线程共享的变量,计算机中的变量实际上是存储在内存中,当程序代码在执行调用时,会从内存中获取该共享变量的内容,然后由各个线程共同使用。例如,利用多线程完成页面抓取任务,为了记录下一共抓取了过少个页面,可设置一个共享变量,记为counts,将其初始值设为0,每抓取一个页面,在其基础上加1,最终的总数就是抓取页面的数量。在多线程模式下,每一个线程都在单独完成页面抓取的任务,但是计数就成为一个问题,共享变量能被每一个线程所调用,这样就借助该变量实现了计数功能的信息共享,也就完成了通信。在这种模式下,各个线程之间相互通信的“中间人”就是counts这一变量。其运行机制的示意图如图1所示。为了保证共享内存的变量具有足够的安全性和可靠性,可将其存储在临界区,并且加锁或者同步。从这种通信方式的实现原理来看,各个线程之间实际上并没有实现直接的通信,而是通过共享变量间接完成通信功能[3]。2) 消息传递。这种通信方式是显性的,各个线程之间通过传递消息来实现直接的通信,典型的如Actor模型,其中每一个actor都具有收件和发件的功能,相互之间借助收发信息来通信。其收件箱类似于一个消息队列,逐次去执行队列中的内容。消息传递的通信方式在应对复杂业务模型时体现出非常强大的适应能力,其应用价值很高。
图1 Java多线程共享内存通信机制
第一,继承Thread类并重写其run()方法。其代码实现为public class ThreadName extends Thread{ public void run(){ /*子线程的代码 */ }}。
第二,利用Runnable接口创建线程类。Java中不能实现多继承,在创建线程类时要先实现Runnanble接口,并且要重写该接口中唯一的方法run(),线程的代码执行体就编写在run()方法中。接口编写完成之后就可直接继承该接口的类的对象。其实现代码如下:public class RunnableTest implements Runnable{ public void run(){ /*线程代码执行体*/ }}。
第三,使用Callable和Future创建线程。以上两种方法虽然都能实现线程创建的目的,但是在具体应用层面却存在一定的缺陷,最主要的问题是代码中不能抛出异常,也不能返回值。Callable 接口是Runnable接口的升级版,其在实现后者功能的基础上还具有返回值,该接口依靠call()方法来执行线程的代码。不过这种方式在具体应用中还需解决一个技术性问题,那就是Callable对象不能直接作为Thread对象的target,这时候就必须引入Future接口[4]。
第一,基于类的调用方式。无论是继承Thread类,还是运用Runnable接口,调用线程时必须先创建出一个类的对象,然后再利用该对象调用相关的方法,执行线程代码。Thread类的调用方式为Threadname test = new ThreadName(),使用Runnable接口创建的类调用线程,其方式为Threadname test = new RunableTest threadname();Thread RunanbleTest = new thread(test),最终创建的线程为thread RunableTest.start()。
第二,其他调用方式。1) wait()方法的作用是立即停止当前线程,使其处于等待的状态,与此同时,使用wait()方法处理的线程将会被置入锁对象的等待队列中。该线程是否再次执行,取决于notify。这一方法使用场景为具有同步作用的代码块。2) notify()方法,其作用是唤醒处于等待状态下的线程,其使用的位置也是必须在具有同步作用的代码块中。3) notifyAll()方法的作用也是唤醒处在等待状态下的线程,但其特点是具有普遍性,所有线程均适用。4) join()方法。这一方法在代码中调用之后,线程就会形成一个队列的机制,当前正在执行的线程会暂时阻塞,join()所属的线程会立即执行其run()方法,等到其所属线程执行完毕之后,再去执行之前受到阻塞的线程的后续代码功能。5) 管道。Java中的线程通信经常采用管道流的方式,在具体应用时要创建管道的输出流和输入流,实现方式分别为PipedOutputStream pos 和 PipedInputStream pis 。将pos赋给信息输入线程,而pis赋给信息获取线程,线程间的通信就此实现。
第三,优化多线程通信的措施。在Java中,服务器和客户端之间的通信非常依赖于Socket对象,该对象生成之后可用于完成请求应答。如果用户发起另一个请求,之前已经完成工作的Socket对象就需要重新连接一次,这样会严重消耗服务器资源。在编程层面可利用ReadMessageThread类来实现多客户端的同步管理,系统的安全性会大幅提升,资源利用率也会更高。另外,sercerSocket.accept()方法在服务器端口执行一次,这就导致服务器和客户端之间的通信只能实现一次,解决这一问题的方法是让服务器和客户端之间的Socket对象形成虚拟连接。
Java多线程技术是其优异性能的重要体现,在多线程技术的支持下,使用Java语言制作的网络系统大多具有运行效率高、高并发负载能力强的优点。但多线程技术在实际应用过程中必须重视通信设计。在技术层面主要依靠消息传递、共享内存等原理。
我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自各大过期杂志,内容仅供学习参考,不准确地方联系删除处理!