java多线程编程核心技术豆瓣,Java多线程详解

  java多线程编程核心技术豆瓣,Java多线程详解

  一、程序、进程、线程程序是一组有序的指令,也可以理解为几行代码。它本身没有任何运行的意义,只是一个静态的实体。可能只是一个简单的文本文件,也可能是编译后生成的可执行文件。

  从狭义上讲,进程是正在运行的程序的实例;从广义上讲,进程是一个程序关于某个数据集的具有某些独立功能的运行活动。进程是操作系统中资源分配的基本单位。

  它是一个线程进程中可以独立执行的最小单元,也是处理器独立调度和分派的基本单元。一个进程可以包含多个线程,每个线程执行自己的任务,同一进程中的所有线程共享进程中的资源,如内存空间、文件句柄等。

  二、多线程编程简介

  1、什么是多线程编程

  多线程编程技术是Java语言的一个重要特征。多线程就是把程序任务分成几个并行的子任务,这些子任务交给多个线程执行。

  多线程编程是一种以线程为基本抽象单元的编程范式。但是多线程编程不仅仅是用多线程编程那么简单,它还有自己的问题需要解决。多线程编程和面向对象编程是兼容的,即我们可以在面向对象编程的基础上实现多线程编程。其实在Java平台一个线程就是一个对象

  2、为什么要使用多线程编程

  现在的计算机往往是多处理器核,每个线程同时只能在一个处理器上运行。如果只用单线程进行开发,就不能充分利用多核处理器的资源来提高程序的执行效率。当多线程用于编程时,不同的线程可以在不同的处理器上运行。这样不仅大大提高了计算机资源的利用率,也提高了程序执行的效率。

  三、JAVA线程API简介

  Java.lang.Thread类是Java平台对线程的实现。Thread类或其子类的实例是一个线程。

  1、线程的创建、启动、运行

  在Java平台中,创建线程是创建线程类(或其子类)的一个例子。每个线程都有自己的任务要执行。线程的任务处理逻辑可以直接在thread类的run方法中实现,也可以由Thread类的run方法调用,所以run方法相当于线程的任务处理逻辑的entry方法,在运行相应的线程时应该由Java虚拟机直接调用,而不是由应用程序代码调用。

  运行线程实际上就是让Java虚拟机执行线程的run方法,这样就可以执行任务处理逻辑代码。如果一个线程没有启动,它的run方法将永远不会被执行。为此,您需要首先启动线程。Thread类的start方法用于启动相应的线程。启动一个线程的本质是请求虚拟机运行相应的线程,而这个线程什么时候可以运行是由线程调度器决定的(线程调度器是操作系统的一部分)。因此,调用线程的start方法并不意味着线程已经开始运行。该线程可能立即开始运行,稍后运行,或者永远不运行。

  这里有两种创建线程的方法(实际上还有其他方法,将在后续文章中详细介绍)。在此之前,我们先来看看Thread类的run方法的源代码:

  //代码1-1 @覆盖

  公共无效运行(){

  如果(目标!=null) {

  target . run();

  }

  }这个run方法在Runnable接口中定义。它不接受参数,也没有返回值。事实上,这是Runnable接口中唯一的方法,所以这个接口是一个函数接口,这意味着我们可以在需要Runnable的地方使用lambda表达式。Thread类实现了这个接口,所以它必须实现这个方法。是targetThread类中的一个域,其类型也是Runnable。目标域表示这个线程需要执行什么,线程类的run方法只执行目标的run方法。

  我们刚刚提到Java虚拟机自动调用线程的run方法。但是Thread类的run方法已经定义好了,所以不能把需要执行的代码放在Thread类的run方法里。因此,我们可以考虑用其他方法来影响run方法的行为。第一种是继承Thread类,重写run方法,让JVM在运行线程时调用我们重写的run方法,而不是Thread类的run方法;第二种方法是把我们要执行的代码传递给Thread类的目标方法,恰好Thread类有几个构造函数可以直接给目标赋值。这样,在调用run方法时,JVM仍然执行我们传递的代码。

  在Java平台中,每个线程都可以有自己的默认名称。当然,我们也可以在构造thread类的实例时给线程一个名字。这个名字便于我们区分不同的线程。

  下面的代码以上述两种方式创建了两个线程。他们的任务很简单。——打印一条欢迎信息,并附上他们自己的名字。

  公共类WelcomeApp {

  公共静态void main(String[] args) {

  thread thread 1=new welcome thread();

  Thread thread2=新线程(()- System.out.println(2。欢迎,我是 Thread.currentThread()。getName()));

  thread 1 . start();

  thread 2 . start();

  }

  }类WelcomeThread扩展线程{

  @覆盖

  公共无效运行(){

  System.out.println(1。欢迎,我是 Thread.currentThread()。getName());

  }

  }以下是该程序运行时的输出:

  1.欢迎,我是Thread-0

  2.欢迎,我是Thread-1多次运行这个程序。我们可以发现,这个程序的输出也可能是:

  2.欢迎,我是线程1

  1.欢迎,我是Thread-0。这说明虽然thread1在thread2之前启动,但并不意味着thread1会在thread2之前运行。

  无论以哪种方式创建线程,一旦线程的run方法(由JVM调用)的执行结束,相应线程的运行也将结束。当然,run方法的执行结束包括正常结束(run方法正常返回)和代码中抛出异常导致的终止。正在运行的线程所占用的资源(比如内存空间)会像其他Java对象一样被JVM回收。

  线程是“一次性物品”,我们不能通过调用一个已经结束运行的线程的start方法让它再次运行。其实start方法只能调用一次,多次调用同一个线程实例的start方法会导致其抛出IllegalThreadStateException异常。

  2、线程的属性

  线程的属性包括线程的编号、名称、类别和优先级,如下表所示:

  如何解决写爬虫IP受阻的问题?立即使用。

  上面提到了守护线程和用户线程的概念,下面简单解释一下。根据线程是否会阻止Java虚拟机正常停止,我们可以把Java中的线程分为守护线程和用户线程(也叫非守护线程)。线程的守护进程属性用于指示相应的线程是否是守护进程。用户会阻止Java虚拟机正常停止,也就是说,一个Java虚拟机只有在其所有用户线程都已经运行完(也就是Thread.run()调用还没有完成)的情况下才能正常停止。但是守护线程不会影响Java虚拟机的正常停止,即守护线程在应用中的运行不会影响Java虚拟机的正常停止。因此,守护线程通常用于执行一些不重要的任务,例如监控其他线程的运行。

  当然,如果强制停止Java虚拟机,比如在Linux系统下使用kill命令强制终止一个Java虚拟机进程,那么即使是用户线程也无法停止Java虚拟机。

  3.线程类的常用方法

  Java中的任何一段代码总是在一个线程中执行。执行当前代码的线程称为当前线程,Thread.currentThread()可以返回当前线程。由于同一个代码可能由不同的线程执行,所以当前线程是相对的,即在代码实际运行时,Thread.currentThread()的返回值可能对应不同的线程(对象)。

  join方法的效果相当于执行该方法的线程和线程调度器说“我要先暂停,等另一个线程运行完才能继续。”

  yield静态方法的效果相当于执行该方法的线程对线程调度器说:“我现在不急,如果别人需要处理器资源,请先用。”当然,如果没有其他人想用,我也不介意继续占用。"

  sleep静态方法的作用相当于执行这个方法的线程对线程调度器说“我要小睡一会儿,过一会儿再叫醒我继续工作。”

  4.线程类中放弃的方法

  虽然这些方法没有相应的替代品,但是可以用其他方法来实现。这部分我们会在后续文章中学习。

  四、无处不在的线程

  Java平台本身就是一个多线程平台。除了Java开发者自己创建和使用的线程,Java虚拟机创建和使用的其他线程在Java平台上随处可见。当然,这些线程也有自己的处理任务。

  Java虚拟机启动时会创建一个主线程(main thread),负责执行Java程序的入口方法(main method)。以下程序打印出主线程的名称:

  公共类MainThreadDemo {

  公共静态void main(String[] args) {

  system . out . println(thread . current thread()。getName());

  }

  }程序会输出“main”,表示main方法被一个名为“main”的线程调用。这个线程是主线程,由JVM创建并启动。

  在多线程编程中,知道哪一个(或哪几个)线程负责一段代码的执行是非常重要的,这关系到性能、线程安全等问题。这将反映在本系列的后续文章中。

  Java虚拟机的垃圾收集器负责回收Java程序中不再使用的内存空间,而这种回收的动作实际上是通过特殊的线程(垃圾收集线程)来实现的,这些线程是Java虚拟机自己创建的。

  为了提高Java代码的执行效率,Java虚拟机中的JIT(Just In Time)编译器会将Java字节码动态编译成Java虚拟机的主机处理器可以直接执行的机器码。这个动态编译过程实际上是由Java虚拟机创建的特殊线程来执行的。

  在Java平台中,线程随处可见,每个线程都有自己的处理任务。

  五、线程的层次关系

  Java平台中的线程并不是孤立的,线程之间总有一些联系。假设线程A执行的代码创建了线程B,那么传统上我们称线程B为线程A的子线程,相应的线程A称为线程B的父线程,比如代码1-2中的线程thread1和thread2是主线程的子线程,主线程是它们的父线程。子线程执行的代码也可以创建其他线程,所以子线程也可以是其他线程的父线程。因此,父线程和子线程是相对的。了解线程的层次关系有助于我们理解Java应用的结构,也有助于我们后面解释其他概念。

  在Java平台中,一个线程是否是守护线程默认取决于它的父线程:默认情况下,父线程是守护线程,子线程也是守护线程;如果父线程是用户线程,则子线程也是用户线程。另外,父线程可以在创建后调用子线程的setDaemon方法,将对应的线程设置为守护线程(或用户线程)。

  线程优先级的默认值是其父线程的优先级,也就是说,如果我们不设置或改变一个线程的优先级,那么这个线程的优先级就等于父线程的优先级。

  但是Java平台中没有API可以获取一个线程的父线程或者一个线程的所有子线程。而且,父线程和子线程的生命周期之间没有必然的联系。比如父线程运行后,子线程可以继续运行,子线程的结束并不妨碍其父线程继续运行。

  六、线程的生命周期状态

  在Java平台中,一个线程从创建、启动到运行结束可能会经历几种状态。如下图所示:

  线程的状态可以通过调用Thread.getState()获得。Thread.getState()的返回值类型是Thread。状态,这是线程类中的一个枚举类型。由线程定义的线程状态。状态包括以下内容:

  新建:已经创建但尚未启动的线程处于这种状态。由于一个线程实例只能启动一次,因此一个线程只能处于这种状态一次。

  RUNNABLE:这个状态可以看作是一个复合状态,它包括两个子状态:READY和RUNNING,但实际上这两个状态并没有在Thread中定义。状态前者意味着处于这种状态的线程可以被线程调度器调度到运行状态。后者表示该状态的线程正在运行,即对应线程对象的run方法对应的指令正在被处理器执行。执行Thread.yield()的线程可能会将其状态从正在运行更改为就绪。处于就绪子状态的线程也称为活动线程。

  阻塞:当一个线程发起阻塞I/O操作或者申请被其他线程持有的独占资源(比如锁)时,对应的线程就会处于这种状态。处于阻塞状态的线程不会占用处理器资源。当阻塞I/o操作完成时,或者线程获得其请求的资源时,线程的状态可以被改变为可运行。

  等待:一个线程执行了一些特定的方法后,就会处于这种等待其他线程执行其他特定操作的状态。可以将其执行线程改为等待状态的方法有:Object.wait()、Thread.join()和LockSupport.park(Object)。对应的可以将对应的线程从等待改为可运行的方法有:Object.notify()/notifyAll()和LockSupport.unpark(Object))。

  TIMED_WAITING:这种状态类似于WAITING,只不过这种状态下的线程不是无限期等待其他线程执行某个特定操作,而是处于有时间限制的等待状态。当其他线程在指定时间内没有执行该线程所期望的特定操作时,该线程的状态会自动变为RUNNABLE。

  终止:已经完成执行的线程处于这种状态。因为一个线程实例只能启动一次,所以一个线程只能处于这种状态一次。run方法的正常返回或者因抛出异常而提前终止,都会导致相应的线程处于这种状态。

  一个线程在其整个生命周期中只能处于新状态和终止状态一次。

  七、多线程编程的优势

  多线程具有以下优势:

  提高系统的吞吐量:多线程编程能够在一个进程中实现多个并发(即同时)操作。例如,当一个线程正在等待I/O操作时,其他线程仍然可以执行它们的操作。

  提高响应性:在多线程编程的情况下,对于GUI软件(比如桌面应用),一个缓慢的操作(比如从服务器下载一个大文件)不会导致软件界面“冻结”,无法响应用户的其他操作;对于Web应用程序,一个请求的缓慢处理不会影响其他请求的处理。

  充分利用多核处理器资源:现在多核处理器的设备越来越普及,甚至手机等消费类设备也普遍使用多核处理器。恰当的多线程编程有助于我们充分利用设备的多核处理器资源,从而避免资源的浪费。

  多线程也有自己的问题和风险,包括以下几个方面:

  线程安全问题。当多个线程共享数据时,如果不采取相应的并发访问控制措施,可能会出现数据一致性问题,例如读取脏数据(过期数据)和丢失更新(某些线程所做的更新被其他线程所做的更新覆盖)。

  线程活动问题。线程从创建到运行结束的整个生命周期都将经历状态。从单个线程的角度来看,可运行状态是我们所期望的。但实际上,不当的代码编写可能会导致某些线程等待其他线程释放锁(阻塞状态),这种情况称为死锁。当然,一直忙的线程也可能有问题。它可能会面临活锁问题,即一个线程一直在尝试一个操作,但就是无法取得进展。此外,线程是一种稀缺的计算资源,一个系统中处理器的数量与系统中线程的数量相比总是非常小的。在某些情况下,可能会出现线程饥饿的问题,即有些线程永远得不到处理器执行的机会,永远处于RUNNABLE状态的READY子状态。

  上下文切换。当处理器从执行一个线程切换到执行另一个线程时,操作系统需要执行的操作称为上下文切换。由于处理器资源的稀缺性,上下文切换可以看作是多线程编程不可避免的副产品,增加了系统的消耗,不利于系统的吞吐量。

  更多相关问题请访问PHP中文网:JAVA视频教程。以上是JAVA中多线程编程方法的详细分析(举例)的详细内容。更多请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: