启动多个java进程则对应多个虚拟机_Java高级编程基础:如何理解进程、线程与并发编程的关系...

69d189a88c50bed6f65d4e1c0321c603.png

并发编程与线程

我们知道随着计算机芯片技术的发展,由最初的单核CPU发展到现在的多核CPU是在集成技术发展到一定程度后,采取的另外一项提高处理能力的方案。

由于多核CPU的出现,使得并行执行成为可能。即使对单个应用程序来说,由于编译器优化和多核处理器的性能优化也会早就很多并发执行的情况。

如此依赖并发编程成为一种编程方案,它主要借助多线程设计方式来完成。

我们常说的并发性是指程序同时执行多个计算的能力。它可以通过将计算分配到一台机器的所有可用CPU内核上,甚至可以被分配到同一网络中的不同机器上来实现。

并发编程与线程

前面我们说过,这种并发能力主要依靠程序的线程来完成,所以,为了更好地理解并行执行,我们必须区分进程和线程。

这么说吧,进程是一段独立运行的程序,它的执行环境是由操作系统亲自安排提供,我们都知道操作系统拥有自己的一组私有资源,比如内存,文件系统等。

而线程则是位于进程内部,使用进程提供的共享资源的多个代码独立执行单元。它们共享进程所提供的比如内存,打开的文件等资源。

由于在同一个进程中,所以多个不同的线程可以共享该进程的所有资源,这让线程能够更好的执行一些复杂的高级任务。

虽然可以在同一台机器上甚至同一网络内的不同机器上运行的不同进程之间建立进程间通信,但是出于性能原因,通常选择线程来在一台机器上并行化计算。

回到我们Java编程中,在Java中,进程应该对应于正在运行的Java虚拟机(JVM),而线程则是位于同一个JVM中,它可以由Java应用程序在运行期间动态创建和停止。

我们开发的每个程序至少有一个线程,我们通常称之为主线程。这个主线程是在每个Java应用程序开始时创建的,它负责调用执行程序的入口main()方法。

从从这个主线程开始,Java应用程序可以根据业务的需要来创建新的线程并使用它们。

Thread类

下面这段代码使用JDK提供的抽象线程特征的类java.lang.Thread中定义的currentThread()方法来访问当前线程。

2d3d8c68fbf3c61e8ba38bfbdd151711.png

从这个简单应用程序的源代码中可以看到,我们直接在main()方法中访问当前线程,并打印出它提供的一些信息:

f11ab84d57dd97fe7aa472897f9f26ad.png

上面输出结果中显示,每个线程都有一个标识符,它在JVM中是唯一的。

我们通常可以用线程的name来通过其它监视工具比如JConsole等来对我们程序的运行进行监视。

这里的优先级priority属性,是当有多个线程需要执行时,用它来决定接下来执行哪。

虽然线程的执行由优先级决定,但是关于线程真正执行的事实是,并不是所有线程都是同时执行的,但是每个CPU内核上的执行时间被划分为小片,下一个时间片被分配给下一个具有最高优先级的等待线程。

我们的JVM会使用其调度程序根据线程的优先级决定接下来执行哪个线程。

除了优先级属性,线程还有一个状态属性state,一个线程在其生命周期中的状态有以下情形:

  • NEW:被创建后尚未启动的线程处于这种状态。
  • RUNNABLE: 在Java虚拟机中执行的线程处于这种状态。
  • BLOCKED: 等待监视器锁而被阻塞的线程处于这种状态。
  • WAITING: 无限期等待另一个线程执行特定操作的线程处于这种状态。
  • TIMED_WAITING:正在等待另一个线程执行某个操作的线程在指定的等待时间内处于这种状态。
  • TERMINATED:已退出的线程处于这种状态。

显然,我们上面示例中的主线程应该是处于RUNNABLE状态。

其中最复杂的一个状态应该是BLOCKED,这样的状态名已经表明线程管理是一个很复杂高级的主题。如果处理不当,线程可能互相阻塞,从而导致应用程序挂起。

最后线程有一个很特别的属性那就是线程组threadGroup,它表明线程是被放在组中管理的。

每个线程都属于一个线程组。Java为其抽象成了java.lang.ThreadGroup类定义,该类提供了一些方法来处理整个线程组的线程。

使用这些整体性方法,我们可以方便的中断组中的所有线程,或者设置它们的最大优先级。

新线程的创建

线程创建和启动了解了线程的基本属性和状态后,我们就开始创建并启动一个线程,来看一下在Java编程中是如何定义和使用线程的。

Java语言中,创建线程基本上有两种方法,分别是Thread类继承方法和Runnable接口实现法。

第一种方式是继承java.lang.Thread类定义一个自己的线程类。

548441899064393b729c9a932b3a3336.png

正如上面所看到的,类MyThread扩展了Thread类并覆盖了方法run()。这里方法run()会在虚拟机JVM上启动新线程后执行。

这里需要注意的是,由于虚拟机必须做一些工作来设置线程的执行环境,所以我们不能直接调用这个方法来启动线程。

我们只能在类MyThread的实例上调用start()方法,由于这个类继承了它的超类中的start()方法,所以这个方法背后的代码告诉JVM为线程分配所有必要的资源并启动它。

当我们运行上面的代码时,我们看到输出。

2df15af07be312b700149dc94b955cf6.png

我们可以看到,run()方法中的代码不是在“main”线程中执行的,而是在我们自己的名为“myThread”的线程中执行的。

实现Runnable接口创建线程

944e804a4937697a11cbc7db0297aefc.png

与上面的子类化实现方式创建一个java.lang.Thread实例不同在于我们提供了一个类来实现Runnable接口,并将它作为参数传递给Thread的构造函数。

同时我们还传递了一个Thread线程名字。

线程创建选择

那么我们到底应该使用子类化方法还是接口方法来创建新的线程呢?在某种程度上取决于个人的习惯和喜好。

相比较而言,实现Runnable接口是一种更轻量级的方法,因为我们只需要做的就是实现接口Runnable。

实现该接口的类仍然可以是其他类的子类,不会发生类型的变化。

而其使用实现接口我们还可以将这个实现类作为参数传递给构造函数,而通过子类化Thread创建的线程,则只能被限制在这个类线程的可用构造函数里,没办法用到其它函数中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值