⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 编写多线程的 java 应用程序——如何避免当前编程中最常见的问题.htm

📁 java的Java书籍JAVA多线程!请大家看看!!有学习的价值!!!xieixe
💻 HTM
📖 第 1 页 / 共 4 页
字号:
   }
}
</CODE>
</PRE></TD></TR></TBODY></TABLE>
            <P><FONT face="Arial, sans-serif" size=-1><B>Fine-grain 
            锁</B><BR>在对象级使用锁通常是一种比较粗糙的方法。为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:</FONT></P>
            <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
            border=1>
              <TBODY>
              <TR>
                <TD><PRE><CODE>class FineGrainLock {

   MyMemberClass x, y;
   Object xlock = new Object(), ylock = new Object();

   public void foo() {
      synchronized(xlock) {
         //access x here
      }

      //do something here - but don't use shared resources

      synchronized(ylock) {
         //access y here
      }
   }

   public void bar() {
      synchronized(this) {
         //access both x and y here
      }
      //do something here - but don't use shared resources
   }
}

</CODE>
</PRE></TD></TR></TBODY></TABLE>
            <P><FONT face="Arial, sans-serif" size=-1>若为了在方法级上同步,不能将整个方法声明为 
            <CODE>synchronized</CODE> 关键字。它们使用的是成员锁,而不是 synchronized 
            方法能够获得的对象级锁。</FONT></P>
            <P><FONT face="Arial, sans-serif" size=-1><A id=4 name=4></A><B 
            class=subhead>信号量</B><BR>通常情况下,可能有多个线程需要访问数目很少的资源。假想在服务器上运行着若干个回答客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程?一种控制访问一组资源的方法(除了简单地上锁之外),就是使用众所周知的信号量计数 
            (counting 
            semaphore)。<I>信号量计数</I>将一组可获得资源的管理封装起来。信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器。例如我们可以将一个信号量初始化为可获得的数据库连接个数。一旦某个线程获得了信号量,可获得的数据库连接数减一。线程消耗完资源并释放该资源时,计数器就会加一。当信号量控制的所有资源都已被占用时,若有线程试图访问此信号量,则会进入阻塞状态,直到有可用资源被释放。</FONT></P>
            <P><FONT face="Arial, sans-serif" 
            size=-1>信号量最常见的用法是解决“消费者-生产者问题”。当一个线程进行工作时,若另外一个线程访问同一共享变量,就可能产生此问题。消费者线程只能在生产者线程完成生产后才能够访问数据。使用信号量来解决这个问题,就需要创建一个初始化为零的信号量,从而让消费者线程访问此信号量时发生阻塞。每当完成单位工作时,生产者线程就会向该信号量发信号(释放资源)。每当消费者线程消费了单位生产结果并需要新的数据单元时,它就会试图再次获取信号量。因此信号量的值就总是等于生产完毕可供消费的数据单元数。这种方法比采用消费者线程不停检查是否有可用数据单元的方法要高效得多。因为消费者线程醒来后,倘若没有找到可用的数据单元,就会再度进入睡眠状态,这样的操作系统开销是非常昂贵的。</FONT></P>
            <P><FONT face="Arial, sans-serif" size=-1>尽管信号量并未直接被 Java 
            语言所支持,却很容易在给对象上锁的基础上实现。一个简单的实现方法如下所示:</FONT></P>
            <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
            border=1>
              <TBODY>
              <TR>
                <TD><PRE><CODE>class Semaphore {
   private int count;
   public Semaphore(int n) {
      this.count = n;
   }

   public synchronized void acquire() {
      while(count == 0) {
         try {
            wait();
         } catch (InterruptedException e) {
            //keep trying
         }
      }
      count--;
   }
    
   public synchronized void release() {
      count++;
      notify(); //alert a thread that's blocking on this semaphore
   }
}
</CODE>
</PRE></TD></TR></TBODY></TABLE>
            <P><FONT face="Arial, sans-serif" size=-1><A id=5 name=5></A><B 
            class=subhead>常见的上锁问题</B><BR>不幸的是,使用上锁会带来其他问题。让我们来看一些常见问题以及相应的解决方法:</FONT></P>
            <DL>
              <DD><FONT face="Arial, sans-serif" 
              size=-1><B>死锁。</B>死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。假如线程 
              "A" 获得了刀,而线程 "B" 获得了叉。线程 A 就会进入阻塞状态来等待获得叉,而线程 B 则阻塞来等待 A 
              所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免死锁问题:<BR></FONT>
              <DD>
              <UL>
                <LI><FONT face="Arial, sans-serif" 
                size=-1>让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 
                的拥有者分别等待对方的资源的问题。</FONT> 
                <LI style="LIST-STYLE-TYPE: none">
                <P><FONT face="Arial, sans-serif" size=-1></FONT></P>
                <LI><FONT face="Arial, sans-serif" 
                size=-1>将多个锁组成一组并放到同一个锁下。前面死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。</FONT> 

                <LI style="LIST-STYLE-TYPE: none">
                <P><FONT face="Arial, sans-serif" size=-1></FONT></P>
                <LI><FONT face="Arial, sans-serif" 
                size=-1>将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。</FONT> 

                <LI style="LIST-STYLE-TYPE: none">
                <P><FONT face="Arial, sans-serif" size=-1></FONT></P>
                <LI><FONT face="Arial, sans-serif" 
                size=-1>最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现死锁的问题。</FONT> 
                </LI></UL>
              <DD><FONT face="Arial, sans-serif" size=-1><B>Volatile 变量.</B> 
              <CODE>volatile</CODE> 关键字是 Java 语言为优化编译器设计的。以下面的代码为例:<BR></FONT>
              <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc 
              border=1>
                <TBODY>
                <TR>
                  <TD><PRE><CODE>class VolatileTest {

   public void foo() {
      boolean flag = false;

      if(flag) {
         //this could happen
      }
   }
}
</CODE>
</PRE></TD></TR></TBODY></TABLE>
              <P><FONT face="Arial, sans-serif" size=-1>一个优化的编译器可能会判断出 
              <CODE>if</CODE> 部分的语句永远不会被执行,就根本不会编译这部分的代码。如果这个类被多线程访问,<CODE> 
              flag</CODE> 被前面某个线程设置之后,在它被 <CODE>if</CODE> 语句测试之前,可以被其他线程重新设置。用 
              <CODE>volatile</CODE> 
              关键字来声明变量,就可以告诉编译器在编译的时候,不需要通过预测变量值来优化这部分的代码。</FONT></P>
              <DD><BR><BR><FONT face="Arial, sans-serif" size=-1></FONT>
              <DD><FONT face="Arial, sans-serif" size=-1><B>无法访问的线程</B> 
              有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。在 Java 编程中 IO 就是这类问题最好的例子。当线程因为对象内的 
              IO 调用而阻塞时,此对象应当仍能被其他线程访问。该对象通常有责任取消这个阻塞的 IO 
              操作。造成阻塞调用的线程常常会令同步任务失败。如果该对象的其他方法也是同步的,当线程被阻塞时,此对象也就相当于被冷冻住了。其他的线程由于不能获得对象的锁,就不能给此对象发消息(例如,取消 
              IO 
              操作)。必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方法。尽管这种方法需要花费一些注意力来保证结果代码安全运行,但它允许在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。</FONT> 

              <DD><BR><BR><FONT face="Arial, sans-serif" size=-1></FONT></DD></DL>
            <P><FONT face="Arial, sans-serif" size=-1><A id=6 name=6></A><B 
            class=subhead>为不同的线程模型进行设计</B><BR>判断是抢占式还是协作式的线程模型,取决于虚拟机的实现者,并根据各种实现而不同。因此,Java 
            开发员必须编写那些能够在两种模型上工作的程序。</FONT></P>
            <P><FONT face="Arial, sans-serif" 
            size=-1>正如前面所提到的,在抢占式模型中线程可以在代码的任何一个部分的中间被打断,除非那是一个原子操作代码块。原子操作代码块中的代码段一旦开始执行,就要在该线程被换出处理器之前执行完毕。在 
            Java 编程中,分配一个小于 32 位的变量空间是一种原子操作,而此外象 <CODE>double</CODE> 和 
            <CODE>long</CODE> 这两个 64 
            位数据类型的分配就不是原子的。使用锁来正确同步共享资源的访问,就足以保证一个多线程程序在抢占式模型下正确工作。</FONT></P>
            <P><FONT face="Arial, sans-serif" 
            size=-1>而在协作式模型中,是否能保证线程正常放弃处理器,不掠夺其他线程的执行时间,则完全取决于程序员。调用 
            <CODE>yield()</CODE> 方法能够将当前的线程从处理器中移出到准备就绪队列中。另一个方法则是调用 
            <CODE>sleep()</CODE> 方法,使线程放弃处理器,并且在 sleep 方法中指定的时间间隔内睡眠。</FONT></P>
            <P><FONT face="Arial, sans-serif" 
            size=-1>正如你所想的那样,将这些方法随意放在代码的某个地方,并不能够保证正常工作。如果线程正拥有一个锁(因为它在一个同步方法或代码块中),则当它调用 
            <CODE>yield()</CODE> 
            时不能够释放这个锁。这就意味着即使这个线程已经被挂起,等待这个锁释放的其他线程依然不能继续运行。为了缓解这个问题,最好不在同步方法中调用 
            <CODE>yield</CODE> 方法。将那些需要同步的代码包在一个同步块中,里面不含有非同步的方法,并且在这些同步代码块之外才调用 
            <CODE>yield</CODE>。</FONT></P>
            <P><FONT face="Arial, sans-serif" size=-1>另外一个解决方法则是调用 
            <CODE>wait()</CODE> 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -