`
85977328
  • 浏览: 1898909 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发(二十一)剖析同步器

 
阅读更多
虽然许多同步器(如锁,信号量,阻塞队列等)功能上各不相同,但它们的内部设计上却差别不大。换句话说,它们内部的的基础部分是相同(或相似)的。

大部分同步器都是用来保护某个区域(临界区)的代码,这些代码可能会被多线程并发访问。要实现这个目标,同步器一般要支持下列功能:
  • 状态
  • 访问条件
  • 状态变化
  • 通知策略
  • Test-and-Set方法
  • Set方法

并不是所有同步器都包含上述部分,也有些并不完全遵照上面的内容。但通常你能从中发现这些部分的一或多个。

状态
同步器中的状态是用来确定某个线程是否有访问权限。在Lock中,状态是boolean类型的,表示当前Lock对象是否处于锁定状态。在BoundedSemaphore中,内部状态包含一个计数器(int类型)和一个上限(int类型),分别表示当前已经获取的许可数和最大可获取的许可数。BlockingQueue的状态是该队列中元素列表以及队列的最大容量。

下面是Lock和BoundedSemaphore中的两个代码片段。
public class Lock{
  //state is kept here
  private boolean isLocked = false;
  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }
  ...
}
public class BoundedSemaphore {
  //state is kept here
  private int signals = 0;
  private int bound   = 0;
  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }
  public synchronized void take() throws InterruptedException{
    while(this.signals == bound) wait();
    this.signal++;
    this.notify();
  }
  ...
}


访问条件
访问条件决定调用test-and-set-state方法的线程是否可以对状态进行设置。访问条件一般是基于同步器状态的。通常是放在一个while循环里,以避免虚假唤醒问题。访问条件的计算结果要么是true要么是false。

Lock中的访问条件只是简单地检查isLocked的值。根据执行的动作是“获取”还是“释放”,BoundedSemaphore中实际上有两个访问条件。如果某个线程想“获取”许可,将检查signals变量是否达到上限;如果某个线程想“释放”许可,将检查signals变量是否为0。

这里有两个来自Lock和BoundedSemaphore的代码片段,它们都有访问条件。注意观察条件是怎样在while循环中检查的。
public class Lock{
  private boolean isLocked = false;
  public synchronized void lock()
  throws InterruptedException{
    //access condition
    while(isLocked){
      wait();
    }
    isLocked = true;
  }
  ...
}
public class BoundedSemaphore {
  private int signals = 0;
  private int bound = 0;
  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }
  public synchronized void take() throws InterruptedException{
    //access condition
    while(this.signals == bound) wait();
    this.signals++;
    this.notify();
  }
  public synchronized void release() throws InterruptedException{
    //access condition
    while(this.signals == 0) wait();
    this.signals--;
    this.notify();
  }
}

状态变化
一旦一个线程获得了临界区的访问权限,它得改变同步器的状态,让其它线程阻塞,防止它们进入临界区。换而言之,这个状态表示正有一个线程在执行临界区的代码。其它线程想要访问临界区的时候,该状态应该影响到访问条件的结果。

在Lock中,通过代码设置isLocked = true来改变状态,在信号量中,改变状态的是signals–或signals++;

这里有两个状态变化的代码片段:
public class Lock{
  private boolean isLocked = false;
  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    //state change
    isLocked = true;
  }
  public synchronized void unlock(){
    //state change
    isLocked = false;
    notify();
  }
}
public class BoundedSemaphore {
  private int signals = 0;
  private int bound   = 0;
  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }
  public synchronized void take() throws InterruptedException{
    while(this.signals == bound) wait();
    //state change
    this.signals++;
    this.notify();
  }
  public synchronized void release() throws InterruptedException{
    while(this.signals == 0) wait();
    //state change
    this.signals--;
    this.notify();
  }
}

通知策略
一旦某个线程改变了同步器的状态,可能需要通知其它等待的线程状态已经变了。因为也许这个状态的变化会让其它线程的访问条件变为true。

通知策略通常分为三种:
  • 通知所有等待的线程
  • 通知N个等待线程中的任意一个
  • 通知N个等待线程中的某个指定的线程

通知所有等待的线程非常简单。所有等待的线程都调用的同一个对象上的wait()方法,某个线程想要通知它们只需在这个对象上调用notifyAll()方法。

通知等待线程中的任意一个也很简单,只需将notifyAll()调用换成notify()即可。调用notify方法没办法确定唤醒的是哪一个线程,也就是“等待线程中的任意一个”。

有时候可能需要通知指定的线程而非任意一个等待的线程。例如,如果你想保证线程被通知的顺序与它们进入同步块的顺序一致,或按某种优先级的顺序来通知。想要实现这种需求,每个等待的线程必须在其自有的对象上调用wait()。当通知线程想要通知某个特定的等待线程时,调用该线程自有对象的notify()方法即可。饥饿和公平中有这样的例子。

下面是通知策略的一个例子(通知任意一个等待线程):
public class Lock{
  private boolean isLocked = false;
  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      //wait strategy - related to notification strategy
      wait();
    }
    isLocked = true;
  }
  public synchronized void unlock(){
    isLocked = false;
    notify(); //notification strategy
  }
}

Test-and-Set方法
同步器中最常见的有两种类型的方法,test-and-set是第一种(set是另一种)。Test-and-set的意思是,调用这个方法的线程检查访问条件,如若满足,该线程设置同步器的内部状态来表示它已经获得了访问权限。

状态的改变通常使其它试图获取访问权限的线程计算条件状态时得到false的结果,但并不一定总是如此。例如,在读写锁中,获取读锁的线程会更新读写锁的状态来表示它获取到了读锁,但是,只要没有线程请求写锁,其它请求读锁的线程也能成功。

test-and-set很有必要是原子的,也就是说在某个线程检查和设置状态期间,不允许有其它线程在test-and-set方法中执行。

test-and-set方法的程序流通常遵照下面的顺序:

如有必要,在检查前先设置状态
检查访问条件
如果访问条件不满足,则等待
如果访问条件满足,设置状态,如有必要还要通知等待线程
下面的ReadWriteLock类的lockWrite()方法展示了test-and-set方法。调用lockWrite()的线程在检查之前先设置状态(writeRequests++)。然后检查canGrantWriteAccess()中的访问条件,如果检查通过,在退出方法之前再次设置内部状态。这个方法中没有去通知等待线程。
public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
    private int writeAccesses    = 0;
    private int writeRequests    = 0;
    private Thread writingThread = null;
    ...
    public synchronized void lockWrite() throws InterruptedException{
      writeRequests++;
      Thread callingThread = Thread.currentThread();
      while(! canGrantWriteAccess(callingThread)){
        wait();
      }
      writeRequests--;
      writeAccesses++;
      writingThread = callingThread;
    }
    ...
}

下面的BoundedSemaphore类有两个test-and-set方法:take()和release()。两个方法都有检查和设置内部状态。

查看源代码打印帮助
public class BoundedSemaphore {
  private int signals = 0;
  private int bound   = 0;
  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }
  public synchronized void take() throws InterruptedException{
    while(this.signals == bound) wait();
    this.signals++;
    this.notify();
  }
  public synchronized void release() throws InterruptedException{
    while(this.signals == 0) wait();
    this.signals--;
    this.notify();
  }
}

set方法
set方法是同步器中常见的第二种方法。set方法仅是设置同步器的内部状态,而不先做检查。set方法的一个典型例子是Lock类中的unlock()方法。持有锁的某个线程总是能够成功解锁,而不需要检查该锁是否处于解锁状态。

set方法的程序流通常如下:

设置内部状态
通知等待的线程
这里是unlock()方法的一个例子:
public class Lock{
  private boolean isLocked = false;
  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}
分享到:
评论

相关推荐

    《java 并发编程实战高清PDF版》

    锁是Java并发编程中用于同步的关键工具。书中深入剖析了各种锁机制,如内置锁(也称为监视器锁),通过`synchronized`关键字实现。此外,还介绍了高级的锁接口`java.util.concurrent.locks`,如`ReentrantLock`,它...

    java并发编程实战(英文版)

    ### Java并发编程实战知识点概述 #### 一、Java并发特性详解 在《Java并发编程实战》这本书中,作者深入浅出地介绍了Java 5.0和Java 6中新增的并发特性。这些特性旨在帮助开发者更高效、安全地编写多线程程序。书中...

    (PDF带目录)《Java 并发编程实战》,java并发实战,并发

    《Java 并发编程实战》是一本专注于Java并发编程的权威指南,对于任何希望深入了解Java多线程和并发控制机制的开发者来说,都是不可或缺的参考资料。这本书深入浅出地介绍了如何在Java环境中有效地管理和控制并发...

    java并发实战中文文档

    《Java并发实战》是一本深度剖析Java并发编程的权威指南,旨在帮助开发者高效地理解和解决多线程环境下的编程挑战。文档清晰明了,配备有详细的目录,使得学习过程更为顺畅,避免了在查找信息上浪费时间。标签“Java...

    java并发编程实战高清版pdf

    Java并发编程涉及到许多关键概念,如线程、同步、锁、并发容器、原子变量以及并发工具类等。以下是一些主要的知识点: 1. **线程基础**:线程是程序执行的最小单位,Java中的`Thread`类提供了创建和管理线程的方法...

    Java并发编程从入门到精通源码.rar

    总的来说,这个资源包涵盖了Java并发编程的核心概念和技术,结合源码分析,学习者可以深入理解并发编程的原理,提升在实际项目中的应用能力。通过实践和调试源码,可以更好地掌握这些知识点,提高解决问题的能力。

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    本文深入探讨了Java并发编程的关键组件——抽象队列同步器(AQS)及其在ReentrantLock的应用。AQS是处理线程同步问题的高效工具,是Java并发编程中的核心。文章首先简要介绍了并发编程领域的先驱Doug Lea。重点在于...

    Java分布式应用学习笔记05多线程下的并发同步器

    `FutureTask`是Java并发编程中非常重要的一个类,它是`Future`接口的一个具体实现。通常用于处理异步计算任务的结果。当多个线程并行执行时,`FutureTask`可以帮助我们收集这些线程的计算结果,并最终整合为一个整体...

    java并发编程艺术

    《Java并发编程艺术》这本书是Java开发者深入理解多线程编程的重要参考资料。它全面而深入地探讨了Java平台上的并发编程技术,对于提升程序性能、优化系统资源利用以及解决多线程环境中的复杂问题有着极大的帮助。...

    Java并发编程实践源码

    通过阅读和分析这些代码,我们可以深入理解Java并发API的用法,如`java.util.concurrent`包下的工具类和接口。 2. **ListenerExamples.java**:此文件可能涉及到事件驱动编程和监听器模式,这是GUI编程中常见的并发...

    java并发编程技术

    Java并发编程技术是Java开发中的重要领域,它涉及到如何在多线程环境下高效地执行程序。并发编程可以充分利用多核处理器资源,提高系统的响应速度和处理能力。以下是一些核心的知识点: 1. **并行程序**:并行程序...

    Java 并发编程实战 中英文+代码示例

    3. **并发工具类**:如Semaphore(信号量)、CountDownLatch(计数器)、CyclicBarrier(回环栅栏)和Exchanger(交换器)等,这些都是Java并发库提供的重要工具,用于实现复杂的同步和协作机制。 4. **并发集合**...

    Java并发编程事件 mobi kindle版

    4. **并发工具类**:深入剖析了Java并发工具类的实现原理,如ConcurrentHashMap、LinkedBlockingQueue、ConcurrentSkipListMap等,并提供了实际应用示例。 5. **线程池**:详细解析了Executor框架,包括线程池的...

    《java并发编程实践》

    《Java并发编程实践》这本书是Java开发者深入了解并发编程的重要参考资料,尤其对于想要提升在多线程环境下编程技能的程序员来说,它提供了丰富的实践经验和深入的理论解析。在Java的世界里,多线程是构建高性能、高...

    JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程

    ### JAVA并发编程与高并发解决方案 #### 一、并发编程基础 ##### 1.1 并发的概念 在计算机科学中,并发是指多个任务或进程同时进行的现象。它不同于并行,后者指的是多个处理器或核心同时执行不同的指令。在Java中...

    java并发编程与高并发解决方案

    通过上述分析,我们可以看出Java并发编程与高并发解决方案涉及到了众多的技术细节和技术栈。在实际应用中,开发者需要根据具体的业务场景选择合适的并发策略和技术方案,以达到最佳的性能表现。此外,随着云计算和...

    java并发编程和网络高级编程pdf

    以下将详细讨论《JAVA并发编程实践》和《Java网络高级编程》两本书中涵盖的关键知识点。 首先,让我们关注并发编程。并发编程是现代多核处理器系统中不可或缺的一部分,它允许程序同时执行多个任务,从而提高系统...

    实战Java高并发程序设计第二版随书代码

    这本书涵盖了Java并发编程的核心概念和技术,旨在帮助开发者在实际项目中高效地处理高并发场景。随书附带的代码提供了丰富的示例,以便读者能够更直观地理解并实践这些理论知识。 1. **Java并发基础** - **线程与...

    JAVA并发面试宝典

    并发编程是Java语言的一个重要特性,对于面试中涉及Java并发编程的问题,需要有深入的理解和掌握。本文档详细列举了Java并发领域面试中常见的问题,以下是对这些问题及答案的解析。 1.1 多线程和并发问题。并发是指...

Global site tag (gtag.js) - Google Analytics