了解Java中线程和锁的复杂性

多线程编程是多线程同步处理的结果。线程是此过程中的基本要素。与多线程相关的复杂性很多。在这里,优锐课小U带大家深入研究Java创建的线程创建,同步和锁方面。

总览

Java中的每个程序都受线程概念的约束。这意味着即使一个最简单的“ Hello World”程序也可以编写,只是执行线程。但是-一个线程。当我们编写多个这样的线程并使它们以同步方式工作时,它将成为一个正常运行的多线程程序。不要被“线程”一词所混淆,因为除非你意识到多线程和多进程之间有区别,否则它可以被视为Java中的进程。我们稍后再处理。但是现在,让我们探讨一个问题:多线程或多进程编程的意义是什么?

多进程

进程通常是根据一组相当线性的指令执行的,偶尔会给出循环,gotos和跳转的指令。最初,在简单的硬件系统下可以。但是,随着时间的推移,随着晶体管数量的增加,处理器发生了巨大的发展。(晶体管是处理器从中获得功率的最细微的部分。最初的想法是:越多越好,摩尔定律。)它们变得越来越紧凑和小巧,易于以更大的处理能力来处理更多的工作。它开始以毫秒为单位完成其任务,而较早的原型则耗时数分钟甚至数小时。因此,在系统忙于I/O工作时,处理器大部分时间处于休眠状态。因此,为了最大程度地利用处理器,程序员开始分批处理它,这意味着将许多任务一起编译并交给处理器,以使处理器忙一会儿。但是,处理器大部分时间都处于睡眠状态,因为程序的很大一部分还处理I/O子系统。结果,一旦I/O工作开始,处理器就可以再次自由进入睡眠状态。处理器与I/O处理几乎没有关系。

这导致了多进程的想法。在多进程中,将分叉一个进程以创建其自身的副本,以便该指令的一部分可以占用其CPU时间,而另一部分则忙于其他工作,例如处理I/O子系统。这样,多个进程可以同时执行,从而使CPU大部分时间保持繁忙。但是,此技术的主要问题是共享资源的同步使用。必须规定,竞争进程不会陷入竞争或僵局的情况,例如,进程P1在释放R1之前在寻找另一个资源(R2)的同时持有资源R1。同时,另一个进程P2可能正在保留R2,并在释放资源之前查找由P1保留的资源R1。简而言之(为了打乱CPU的睡眠,程序员开始了不眠之夜),这是一个复杂的场景,但是有一些方法可以通过信号量,互斥量等技术来克服它们。现在不要进入。

但是,多线程适用于何处?

好吧,多进程很好,但是多线程更好。就game而言,多线程和多进程意味着同一件事——使CPU忙于处理多组共享指令。两种情况都有助于产生新的并发流程。毫无疑问,这两者在具有多核CPU的机器上都是有效的,在机器上可以调度进程流在空闲处理器之间跳转,从而通过并行和分布式处理利用执行速度。但是,区别在于线程一词。与进程不同,线程对资源的需求较少。它比派生或生成进程的开销要少。线程不会像进程一样分配新的系统虚拟内存空间和环境。线程共享相同的地址空间,并通过定义一个函数来产生它们,并且其参数由线程处理。线程不仅在多核系统上有效,而且可以通过利用I/O的延迟以及其他导致执行停止的系统功能来提高单个处理系统的性能。

但是,多进程或多线程本身都不是并行编程。它们很好地利用了非分布式环境中的效率。并行编程是完全不同的模型。有专门用于分布式计算环境的技术,例如MPIPVM。线程的游乐场被限制在单个计算机系统内。

Java中的线程如何工作

Java中的线程是通过扩展Thread类或Runnable接口来实现的。run()方法在扩展类中被覆盖。 请注意,JVM会从底层操作系统获取创建和执行线程所需的所有帮助。实际上,线程的创建和执行是从JNI函数调用开始的,后者依次使用POSIX API(在Linux中)创建和执行线程。 java.lang中的Thread类定义如下:

package java.lang;
// ...
public class Thread implements Runnable {
   // ...
   public synchronized void start() {
      if (threadStatus != 0)
         throw new IllegalThreadStateException();
 
      group.add(this);
 
      Boolean started = false;
      try {
         start0();
         started = true;
      } finally {
         try {
            if (!started) {
               group.threadStartFailed(this);
            }
         } catch (Throwable ignore) {
         /* do nothing. If start0 threw a Throwable then
            it will be passed up the call stack */
         }
      }
   }
   // ...
   private native void start0();
   //  ...
   private native void setPriority0(int newPriority);
   private native void stop0(Object o);
   private native void suspend0();
   private native void resume0();
   private native void interrupt0();
   private native void setNativeName(String name);
   // ...
}


Java创建Thread对象时,对start()方法的调用将启动一个新线程。请注意,start()方法调用了start0()本机方法。start0()是使用C/C++编写的特定于平台的本机方法。通过JNI调用此方法。JNI是本机方法接口的规范,描述了Java如何与用本机代码编写的程序进行交互以及如何将其集成到Java中。请参阅Java本机接口概述。

Linux JVM中,Java线程使用POSIX API调用作为其本机实现。

因此,对POSIX线程API的研究可以揭示Java线程内部功能的更多复杂细节。

线程同步和锁

显然,当执行多个线程时,必须有某种机制可以在线程之间建立通信。在Java中,线程可以通过几种方式相互通信。但是,在多线程通信期间发生的问题是同步,没有同步,一个线程可能会不一致地修改共享数据,而另一线程正在更新共享数据。此结果是错误的,必须避免。

处理同步的基本方法之一是使用监视器。每个Java对象都与一个监视器关联。监视器可以通过线程锁定或解锁,但前提条件是只有一个线程可以在监视器上保持锁。尝试锁定监视器的任何其他线程都将被阻止,直到释放它为止。

Java提供了一个关键字来进行同步语句。指定的同步语句将阻止所有尝试访问监视器锁的尝试,直到成功完成对象监视器的锁操作为止。一旦进行了锁定,就只能执行同步的语句。程序员不必费心;成功或突然完成后,将在同一监视器上自动执行解锁操作。因此,同步块中的语句可确保一致性。

因此,Java不需要检测任何死锁条件,也不会阻止死锁条件。 根据Java语言规范,...线程在多个对象上(直接或间接)持有锁的程序应使用常规技术来避免死锁,并在必要时创建不死锁的高级锁定原语。

// Ensures that only one thread can execute at a time the sync
// object is the reference whose lock associates with the monitor
// the code within this block ensures synchronized execution
 
synchronized (sync_obj) {
 
  // Process shared resources and variables
 
}


结论

从长远角度来看,在Java中使用线程仅具有肤浅的理解可能会造成混淆和直觉。在这里,我们试图触及多线程的两个最重要方面,尽管尚不完整,但仍足以使研究朝着更好的理解方向发展。多线程还有其他复杂的方面,我们将在随后的内容中提示要寻找的内容和内容。

欢迎留言或私信探讨学习~

 


猜你喜欢

转载自blog.51cto.com/14631216/2459518