Java虚拟机之线程同步

来源:互联网 发布:淘宝网短裤套装 编辑:程序博客网 时间:2024/05/20 14:23

        对于多线程,我们更多地是在操作系统中接触到这个概念,但是Java却在语言级支持多线程,这也是Java语言的一大优势。Java所使用的线程同步机制是监视器。

1. 监视器

Java监视器支持两种线程同步:互斥和协作。
互斥:通过对象锁来实现,允许多个线程在同一个共享数据上独立而不干扰地工作。
协作:通过Object类的wait方法和notify方法来实现,允许多个线程为了同一个目标而共同工作。

1.1 监视器的互斥

    Java虚拟机为每个对象分配一个监视器。当多个同步线程需要对同一对象进行操作或者执行该对象的同步方法的时候,它们之间必须竞争该对象的监视器。只有获得该对象的监视器的线程才能执行,其他线程必须等待该线程释放了该对象的监视器以后,再去竞争该对象的监视器。
    监视器除了同步一些数据之外,还涉及到一些代码,也就是所谓的监视区域。对一个监视器来说,监视区域是最小的,不可分割的代码块。同一个监视器中,监视区域只会被一个线程执行,线程只有获得了监视区域的监视器才能执行。
    当一个线程到达了一个监视区域的开始处,它就会放置到监视器的入口区。如果没有其他线程等待,也没有线程正持有该监视器,它就会获得监视器。当执行完监视区域的代码后,就会释放并退出监视器。如果当前监视器正在被另外一个线程持有,那么它就会进入一个等待队列。当持有线程退出之后,等待队列中的线程会竞争该监视器。最终只能有一个线程持有该监视器,其他的线程仍会留在等待队列中。

1.2 监视器的协作

    当一个线程需要一些特别的数据,而由另一个线程负责改变这些数据的状态时,同步显得特别重要。Java虚拟机用于同步的监视器叫“等待并唤醒”监视器。
    这种监视器中,一个已经持有该监视器的线程,可以通过调用等待命令,暂停自身的执行。当这个线程执行等待命令后,就会释放该监视器并进入一个等待区,并会一直在那里持续等待状态,直到一段时间后该监视器内的其他线程调用了唤醒命令。当一个线程调用唤醒命令后,它会持续持有监视器,直到它主动释放监视器,如执行了一条等待命令或者执行完监视区域。当执行唤醒的线程释放监视器后,等待线程会苏醒,其中的一个等待线程会重新获得监视器。

2. JVM线程同步的实现

    JVM为每个对象和类都关联一个监视器,在语言层次上用同步方法或者同步语句标识监视区域,每个对象都实现等待/通知方法等方式来实现线程同步。

2.1 对象锁

    JVM中的每个线程都有一个独立的栈,它们之间互不干扰,所以无需同步栈中的数据。而每个JVM中只有一个堆和一个方法区,堆中的实例变量和方法区中的类变量会被所有线程所共享,所以需要进行同步。
    JVM为每个对象和类都关联一个对象锁,来通过监视器的机制实现对某个对象的互斥访问和基于某条件的协作。对象的对象锁针对的是java.lang.Object实例对象,而类的对象锁针对的是java.lang.Class类对象。
    JVM通过一个为每个对象锁分配一个计数器,来记录对象被加锁的次数。对象锁具有如下特性:
  —对象没有被锁,计数器为0,访问它的线程可以对它加锁;
  —该对象锁已被某线程获得,其他线程必须等待,直到该线程释放对象锁
  —一个线程已经获得对象锁,则它可再次对该对象加锁,每加锁一次,计数器加1
  —拥有对象锁的线程释放对象锁时,计数器减1;直到计数器减为0时,等待该锁的线程竞争使用它

2.2 语言层次的线程同步

   Java语言层次对线程同步的支持,主要包括对监视区域的标识和协作方法。
   Java语言对监视区域的标识是通过同步方法和同步语句实现的。同步方法是在类方法前面用synchronized关键字声明。同步语句是把需要同步的语句用synchronized关键字标识出并指名同步语句所针对的对象。
   Java语言提供的协作方法有wait()、notify()和notifyAll()三个,它们都是java.lang.Object类的方法。由于Java语言的每个对象都是java.lang.Object的子类,因此所有对象都实现了线程协作的方法。
  • wait():拥有对象锁的线程释放该对象锁,并进入等待状态
  • notify()/notifyAll():唤醒该对象锁的等待队列中的线程,具体唤醒哪个线程则是不确定的,取决于JVM设计者。notify()随意唤醒等待队列中的一个线程;notifyAll()则唤醒等待队列中的所有线程。
原创粉丝点击