Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 694|回复: 0

Java并发编程-同步(六)

[复制链接]

该用户从未签到

发表于 2011-9-13 14:49:57 | 显示全部楼层 |阅读模式
线程除要对共享数据保证互斥性访问外,往往还需保证线程的操作按照特定顺序进行。解决多线程按照特定顺序访问共享数据的技术称作同步。同步技术最常见的编程范式是同步保护块。这种编程范式在操作前先检测某种条件是否成立,如成立则继续操作;如不成立则有两种选择,一种是简单的循环检测,直至此条件条件成立:
public void guardedOperation(){
  while(!condition_expression){
    System.out.println("Not ready yet, I have to wait again!");
  }
}
    这种方法非常消耗CPU资源,任何情况下都不应该使用这种方法。另种更好的方式是条件不成立时调用Object.wait方法挂起当前线程,使它一直等待,直至另一个线程发出激活事件。当然该事件不一定是当前线程希望等待的事件。
public synchronized guardedOperation() {
    while(!condition_expression) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Now, condition met and it is ready!");
}
    这儿有两点需要特别注意:
1.要在循环检测中等待条件满足,这是因为中断事件并不一定是当前线程所期望的事件。线程等待被中断后应该继续检测条件,以便决定是否进入下一轮等待。
2.当前线程在对wait方法调用时,必须是已经获得wait方法所属对象的内部锁。也就是说,wait方法必须在互斥块或者互斥方法体内调用,否则就会发生NotOwnerException错误。这种限制和前面所说的同步前提是互斥的说法是一致的。
    上面代码更通用的写法是:
...
synchronized(lock){
   while(!condition_expression){
      try{
         lock.wait();
      }catch(InterruptedException ie){}
   }  
   System.out.println("Now, condition met and it is ready!");  
}
...
    线程在synchronized语句获取对象的内部锁之后,在synchronized代码块期间就拥有了内部锁。当判断条件不成立时,可以调用该对象的wait方法进入等待状态。
    注意持有锁的线程在调用wait方法进入等待状态之后,会自动释放持有的锁。这样做的目的是允许其他的线程进入临界区继续操作,以防止死锁的发生。
    举生产者和消费者的例子。如果消费者在检查时发现没有产品生成,则调用wait方法等待生产者生产。如果此时消费者不释放该锁,生产者就会因为获取不到该锁而处于阻塞状态。而此时消费者却在等待生产者生产出产品来,这样双方就进入死锁状态。因此wait方法需要在挂起线程后释放该线程所拥有的锁。
    当wait方法调用后,线程进入等待状态,直至未来某刻其他线程获得该锁并调用其invokeAll(或invoke)方法将其唤醒。该线程通过如下类似的代码激活等待在此锁上的线程:
public synchronized notifyOperation(){
   condition_expression=true;
   notifyAll();
}
    假设线程C因检测到某种条件不满足而进入等待状态,激活C线程的P线程往往需要和C线程建立“发生过”关系。也就是说程序期望线程P和C之间按照先P后C的顺序执行。对于生产者和消费者例子来说,P就是生产者,C就是消费者,它们之间存在从P到C的“发生过”关系。
    线程P在调用notify或者notifyAll方法时需要首先获得该对象的锁,因此这些代码也需要放在synchronized代码体内。上面的激活方法更通用的写法是:
  ...
  synchronized(lock){
     condition_expression=true;
     lock.notifyAll();
  }
  ...
    现举生产者和消费者之间同步的例子。为了简化,假设生产者和消费者之间只共享一个容器。生产者生产出对象后放在在该容器中,而消费者从该容器中获取该对象进行消费。消费者和生者之间往往需要建立双向的“发生过”关系,即消费者只有在有东西才能消费,而生产者只有在有存放空间时才能生产。这儿为了简化,只假定保证消费者有东西可消费,生产者不管是否有空间可存放,只是将对象生产出来放在容器中。下面是这个例子的代码:
public class TankContainer{
   private Tank tank;
   public synchronized void putTank(Tank tank){
      //Dont bother to check whether it has room.
      this.tank=tank;
      notifyAll();
   }
   public synchronized Tank getTank(){
      //Check whether there's tank to consume
      while(tank==null){
         //No tank yet, let's wait.
         try{
             wait();
         }catch(InterruptedException e){}
      }
      Tank retValue=tank.
      tank=null; //Clear tank.
      return retValue;
   }
}
public ProducerThread extends Thread{
  //Shared TankContainer
  private TankContainer container;
  public ProducerThread(TankContainer container){
    this.container=container;
  }
  ...
  public void run(){
    while(true){
       Tank tank=produceTank();
       container.putTank(tank);   
    }
  }
  ...
}
public ConsumerThread extends Thread{
  //Shared TankContainer
  private TankContainer container;
  public ConsumerThread(TankContainer container){
    this.container=container;
  }
  ...
  public void run(){
    while(true){
      Tank tank=container.getTank();
      consumeTank(tank);     
    }
  }
  ...
}
public class ProducerConsumer{
  public static void main(String[]args){
    TankContainer container=new TankContainer();//Shared TankContainer
    new ProducerThread(container).start(); //Start to produce goods in its own thread.
    new ConsumerThread(container).start(); //Start to consume goods in its own thread.
  }
}
     总结一下,同步编程时应该要记住下面几条:
1.两个线程应该获取同一个对象的锁。这是获取同步的互斥性前提。
2.消费者线程应在循环体内检测条件是否成立。
3.消费者线程在条件没有满足时应调用锁对象的wait方法等待。
4.wait方法被中断后应进入下一轮条件检测循环。
5.生产者线程应该在其操作或结束返回之前调用锁对象的notify或notifyAll方法激活等待线程。
   补充一下notify和notifyAll方法的区别。notify激活等待队列上的下一个线程。而notifyAll则激活所有等待线程。在生产者释放锁之后,这些被激活线程竞争获取该锁。获得该锁的线程只有一个,它从wait中返回,进入下一轮条件检测。没有获得锁的线程继续进入等待状态,等待下一次激活事件。

    java中除了通过互斥和同步技术来获得代码线程安全共性以外,还通过所谓恒量对象(immutable objects)的模式获取线程安全性。其基本原理是恒量对象在创建完毕后就只能读取,就像final对象一样。后面的文章将对immuable对象技术进行详细描述。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

GMT+8, 2024-5-5 06:28 , Processed in 0.490413 second(s), 46 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表