Java多线程学习笔记(二)

原创 2003年06月22日 14:20:00

Java多线程学习笔记(二)

作者:陶冶(无邪)
时间:2003.6.21

四、Java的等待通知机制
  在有些时候,我们需要在几个或多个线程中按照一定的秩序来共享一定的资源。例如生产者--消费者的关系,在这一对关系中实际情况总是先有生产者生产了产品后,消费者才有可能消费;又如在父--子关系中,总是先有父亲,然后才能有儿子。然而在没有引入等待通知机制前,我们得到的情况却常常是错误的。这里我引入《用线程获得强大的功能》一文中的生产者--消费者的例子:
/* ==================================================================================
 * 文件:ThreadDemo07.java
 * 描述:生产者--消费者
 * 注:其中的一些注释是我根据自己的理解加注的
 * ==================================================================================
 */

// 共享的数据对象
 class ShareData{
  private char c;
  
  public void setShareChar(char c){
   this.c = c;
  }
  
  public char getShareChar(){
   return this.c;
  }
 }
 
 // 生产者线程
 class Producer extends Thread{
  
  private ShareData s;
  
  Producer(ShareData s){
   this.s = s;
  }
  
  public void run(){
   for (char ch = 'A'; ch <= 'Z'; ch++){
    try{
     Thread.sleep((int)Math.random() * 4000);
    }catch(InterruptedException e){}
    
    // 生产
    s.setShareChar(ch);
    System.out.println(ch + " producer by producer.");
   }
  }
 }
 
 // 消费者线程
 class Consumer extends Thread{
  
  private ShareData s;
  
  Consumer(ShareData s){
   this.s = s;
  }
  
  public void run(){
   char ch;
   
   do{
    try{
     Thread.sleep((int)Math.random() * 4000);
    }catch(InterruptedException e){}
    // 消费
    ch = s.getShareChar();
    System.out.println(ch + " consumer by consumer.");
   }while(ch != 'Z');
  }
 }

class Test{
 public static void main(String argv[]){
  ShareData s = new ShareData();
  new Consumer(s).start();
  new Producer(s).start();
 }
}

  在以上的程序中,模拟了生产者和消费者的关系,生产者在一个循环中不断生产了从A-Z的共享数据,而消费者则不断地消费生产者生产的A-Z的共享数据。我们开始已经说过,在这一对关系中,必须先有生产者生产,才能有消费者消费。但如果运行我们上面这个程序,结果却出现了在生产者没有生产之前,消费都就已经开始消费了或者是生产者生产了却未能被消费者消费这种反常现象。为了解决这一问题,引入了等待通知(wait/notify)机制如下:
  1、在生产者没有生产之前,通知消费者等待;在生产者生产之后,马上通知消费者消费。
  2、在消费者消费了之后,通知生产者已经消费完,需要生产。
下面修改以上的例子(源自《用线程获得强大的功能》一文):

/* ==================================================================================
 * 文件:ThreadDemo08.java
 * 描述:生产者--消费者
 * 注:其中的一些注释是我根据自己的理解加注的
 * ==================================================================================
 */

class ShareData{
 
 private char c;
 // 通知变量
 private boolean writeable = true;

 // ------------------------------------------------------------------------- 
 // 需要注意的是:在调用wait()方法时,需要把它放到一个同步段里,否则将会出现
 // "java.lang.IllegalMonitorStateException: current thread not owner"的异常。
 // -------------------------------------------------------------------------
 public synchronized void setShareChar(char c){
  if (!writeable){
   try{
    // 未消费等待
    wait();
   }catch(InterruptedException e){}
  }
  
  this.c = c;
  // 标记已经生产
  writeable = false;
  // 通知消费者已经生产,可以消费
  notify();
 }
 
 public synchronized char getShareChar(){
  if (writeable){
   try{
    // 未生产等待
    wait();
   }catch(InterruptedException e){}  
  }
  // 标记已经消费
  writeable = true;
  // 通知需要生产
  notify();
  return this.c;
 }
}

// 生产者线程
class Producer extends Thread{
 
 private ShareData s;
 
 Producer(ShareData s){
  this.s = s;
 }
 
 public void run(){
  for (char ch = 'A'; ch <= 'Z'; ch++){
   try{
    Thread.sleep((int)Math.random() * 400);
   }catch(InterruptedException e){}
   
   s.setShareChar(ch);
   System.out.println(ch + " producer by producer.");
  }
 }
}

// 消费者线程
class Consumer extends Thread{
 
 private ShareData s;
 
 Consumer(ShareData s){
  this.s = s;
 }
 
 public void run(){
  char ch;
  
  do{
   try{
    Thread.sleep((int)Math.random() * 400);
   }catch(InterruptedException e){}
  
   ch = s.getShareChar();
   System.out.println(ch + " consumer by consumer.**");
  }while (ch != 'Z');
 }
}

class Test{
 public static void main(String argv[]){
  ShareData s = new ShareData();
  new Consumer(s).start();
  new Producer(s).start();
 }
}

  在以上程序中,设置了一个通知变量,每次在生产者生产和消费者消费之前,都测试通知变量,检查是否可以生产或消费。最开始设置通知变量为true,表示还未生产,在这时候,消费者需要消费,于时修改了通知变量,调用notify()发出通知。这时由于生产者得到通知,生产出第一个产品,修改通知变量,向消费者发出通知。这时如果生产者想要继续生产,但因为检测到通知变量为false,得知消费者还没有生产,所以调用wait()进入等待状态。因此,最后的结果,是生产者每生产一个,就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。

五、线程的中断
  在很多时候,我们需要在一个线程中调控另一个线程,这时我们就要用到线程的中断。用最简单的话也许可以说它就相当于播放机中的暂停一样,当第一次按下暂停时,播放器停止播放,再一次按下暂停时,继续从刚才暂停的地方开始重新播放。而在Java中,这个暂停按钮就是Interrupt()方法。在第一次调用interrupt()方法时,线程中断;当再一次调用interrupt()方法时,线程继续运行直到终止。这里依然引用《用线程获得强大功能》一文中的程序片断,但为了更方便看到中断的过程,我在原程序的基础上作了些改进,程序如下:

/* ===================================================================================
 * 文件:ThreadDemo09.java
 * 描述:线程的中断
 * ===================================================================================
 */
class ThreadA extends Thread{
 
 private Thread thdOther;
 
 ThreadA(Thread thdOther){
  this.thdOther = thdOther;
 }
 
 public void run(){
  
  System.out.println(getName() + " 运行...");
  
  int sleepTime = (int)(Math.random() * 10000);
  System.out.println(getName() + " 睡眠 " + sleepTime
   + " 毫秒。");
  
  try{
   Thread.sleep(sleepTime);
  }catch(InterruptedException e){}
  
  System.out.println(getName() + " 觉醒,即将中断线程 B。");
  // 中断线程B,线程B暂停运行
  thdOther.interrupt();
 }
}

class ThreadB extends Thread{
 int count = 0;
 
 public void run(){
  
  System.out.println(getName() + " 运行...");
  
  while (!this.isInterrupted()){
   System.out.println(getName() + " 运行中 " + count++);
      
   try{  
    Thread.sleep(10);
   }catch(InterruptedException e){
    int sleepTime = (int)(Math.random() * 10000);
    System.out.println(getName() + " 睡眠" + sleepTime
     + " 毫秒。觉醒后立即运行直到终止。");
    
    try{
     Thread.sleep(sleepTime);
    }catch(InterruptedException m){}
    
    System.out.println(getName() + " 已经觉醒,运行终止...");
    // 重新设置标记,继续运行 
    this.interrupt();
   }
  }
  
  System.out.println(getName() + " 终止。");  
 }
}

class Test{
 public static void main(String argv[]){
  ThreadB thdb = new ThreadB();
  thdb.setName("ThreadB");
  
  ThreadA thda = new ThreadA(thdb);
  thda.setName("ThreadA");
  
  thdb.start();
  thda.start();
 }
}

  运行以上程序,你可以清楚地看到中断的过程。首先线程B开始运行,接着运行线程A,在线程A睡眠一段时间觉醒后,调用interrupt()方法中断线程B,此是可能B正在睡眠,觉醒后掏出一个InterruptedException异常,执行其中的语句,为了更清楚地看到线程的中断恢复,我在InterruptedException异常后增加了一次睡眠,当睡眠结束后,线程B调用自身的interrupt()方法恢复中断,这时测试isInterrupt()返回true,线程退出。

更多的线程组及相关的更详细的信息,请参考《用线程获得强大功能》一文及《Thinking in Java》。

Java多线程知识小抄集(一)

本文主要整理博主遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1. in...
  • u013256816
  • u013256816
  • 2016年05月05日 18:23
  • 13528

Java实现二值化处理图像

由于需求,在做图像处理这块,大概也学习了小半年,本文利用Java通过设置一个阈值来读一图像进行二值化处理。 import java.awt.Color; import java.awt.image.B...
  • xiaoxun2802
  • xiaoxun2802
  • 2017年02月25日 09:31
  • 1373

阿里二面准备(Java 研发)

感觉有机会进行二面(原谅我没来由的自信,~~),准备一下。参考了牛客网上 30 多个面经帖,这是目前我能找到的几乎所有的问题。私以为如果能全部掌握,基本就能收割 offer 了。时间有限的话,针对自己...
  • u012516166
  • u012516166
  • 2017年07月27日 13:16
  • 828

《Java多线程编程核心技术》推荐

写这篇博客主要是给猿友们推荐一本书《Java多线程编程核心技术》。之所以要推荐它,主要因为这本书写得十分通俗易懂,以实例贯穿整本书,使得原本抽象的概念,理解起来不再抽象。只要你有一点点Java基础,你...
  • u013142781
  • u013142781
  • 2016年03月04日 21:35
  • 19156

JAVA灰度化、二值化图片如此简单方便

JAVA灰度化、二值化图片如此简单方便 分类: java 算法2011-12-30 19:07 5589人阅读 评论(7) 收藏 举报 java图片灰度化图片二直化 ...
  • JJRFJYFJYFJDFJRUJDJD
  • JJRFJYFJYFJDFJRUJDJD
  • 2015年01月26日 14:17
  • 1749

JAVA简单二值化图像处理

package downloadimg; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.Buf...
  • Cytosine
  • Cytosine
  • 2017年02月06日 16:32
  • 367

java多线程入门例子-第一例

– 认识多线程-1 多线程运行顺序基础举例 多线程数据共享基本例子
  • sinat_30162657
  • sinat_30162657
  • 2016年09月18日 22:22
  • 1471

Java多线程学习(吐血超详细总结)

转自:http://www.mamicode.com/info-detail-517008.html 目录(?)[-] 一扩展javalangThread类二实现javalangRun...
  • gf771115
  • gf771115
  • 2016年06月15日 15:15
  • 41125

java多线程核心技术梳理(附源码)

本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock的使用,定时器,单例模式,以及线程状态与线程组。...
  • h3243212
  • h3243212
  • 2016年04月18日 15:48
  • 7117

多线程——Java多线程实现的三种方式

实现多线程的几种方式,建议使用runable实现,不管如何最终都需要thread.start( )来启动线程。...
  • xdd19910505
  • xdd19910505
  • 2016年03月22日 20:30
  • 1948
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java多线程学习笔记(二)
举报原因:
原因补充:

(最多只允许输入30个字)