注册 | 登录 忘记密码? 51cto首页 | 博客 | 论坛 | 招聘
热点文章 2003 resource kit之Roboc..
 帮助

java 线程机制


2008-01-15 12:32:11
          Bruce Eckel将线程的应用定义为如何给CPU分配时间来提高利用率,优秀的代码都必须考虑线程,尤其要用到共享资源的时候。
         我们都知道当前的Windows操作系统是一个“多线程”操作系统。那么什么是线程呢?线程就是进程中的一个实体,它和进程一样能够独立的执行控制,由操作系统负责调度,其区别就在于线程没有独立的存储空间,而是与同属于一个进程的其他线程共享一个存储空间,这使得多线程之间的通信较进程简单,并且多线程的执行都是并发而且是相互独立的。为了运行所有这些线程,操作系统为每个独立线程安排一些CPU 时间,操作系统以轮转方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。
        声明:
        大部分内容转自http://www.3lian.com/zl/2004/10-5/222237.html,作者不知道是谁。只有最后一个实例是经过本人改过的,里面添加了一些必要的注释,有助于大家对线程的理解。
        在Java中实现一个线程有两种方法,第一是实现Runnable接口实现它的run()方法,第二种是继承Thread类,覆盖它的run()方法。下面是代码示例:
     
 public class DoSomething implements Runnable {
  public void run(){
  // here is where you do something
  }
  }
  public class DoAnotherThing extends Thread {
  public void run(){
  // here is where you do something
  }
  }  
        这两种方法的区别是,如果你的类已经继承了其它的类,那么你只能选择实现Runnable接口了,因为Java只允许单继承的。
  Java中的线程有四种状态分别是:运行、就绪、挂起、结束。如果一个线程结束了也就说明他是一个死线程了。当你调用一个线程实例的start()的方法的时候,这个时候线程进入就绪状态,注意并不是运行状态,当虚拟机开始分配给他CPU的运行时间片的时候线程开始进入运行状态,当线程进入等待状态,例如等待某个事件发生的时候,这时候线程处于挂起状态。
  启动一个线程你只需要调用start()方法,针对两种实现线程的方法也有两种启动线程的方法,分别如下:
 
DoSomething doIt = new DoSomething();
  Thread myThread = new Thread( doIt );
  myThread.start();
  DoAnotherThing doIt = new DoAnotherThing();
  doIt.start();
        由于安全等因素Thread中的stop()方法已经不推荐使用了,因此如果你想要停止一个线程的时候可以通过设置一个信号量,例如:
      
public class MyThread implements Runnable {
  private boolean quit = false;
  public void run(){
  while( !quit ){
  // do something
  }
  }

  public void quit(){
  quit = true;
  }
  }

       如果每个线程只做它自己的事情,那么就很简单了,但是有的时候几个线程可能要同时访问一个对象并可能对它进行修改,这个时候你必须使用线程的同步在方法或者代码块使用关键字synchronized,例如:      
 public class Counter {
  private int counter;
  public synchronized int increment(){
  return ++counter;
  }

  public synchronized int decrement(){
  if( --counter < 0 ){
  counter = 0;
  }

  return counter;
  }
  }

         每个java对象都可以最为一个监视器,当线程访问它的synchronized方法的时候,他只允许在一个时间只有一个线程对他访问,让其他得线程排队等候。这样就可以避免多线程对共享数据造成破坏。记住synchronized是会耗费系统资源降低程序执行效率的,因此一定要在需要同步的时候才使用,尤其在J2ME的开发中要小心。
  如果你要是想让线程等待某个事件的发生然后继续执行的话,那么这就涉及到线程的调度了。在java中通过wait(),notify(),notifyAll()来实现,这三个方法是在Object类中定义的,当你想让线程挂起的时候调用obj.wait()方法,在同样的obj上调用notify()则让线程重新开始运行。 最后以SUN提供的Producer/Consumer的例子来结束这篇文章,内容是Producer产生一个数字而Consumer消费这个数字,这个小程序里面基本覆盖了本文所有的知识点。请详细研究一下代码
每个java对象都可以最为一个监视器,当线程访问它的synchronized方法的时候,他只允许在一个时间只有一个线程对他访问,让其他得线程排队等候。这样就可以避免多线程对共享数据造成破坏。记住synchronized是会耗费系统资源降低程序执行效率的,因此一定要在需要同步的时候才使用,尤其在J2ME的开发中要小心。
  如果你要是想让线程等待某个事件的发生然后继续执行的话,那么这就涉及到线程的调度了。在java中通过wait(),notify(),notifyAll()来实现,这三个方法是在Object类中定义的,当你想让线程挂起的时候调用obj.wait()方法,在同样的obj上调用notify()则让线程重新开始运行。 最后以SUN提供的Producer/Consumer的例子来结束这篇文章,内容是Producer产生一个数字而Consumer消费这个数字,这个小程序里面基本覆盖了本文所有的知识点。请详细研究一下代码
          本人改了一个sun提供的线程实例,注释都在代码里,
package org.bjtu.cn;

public class ProducerConsumerTest {

    public static void main(String[] args) {
  
  CubbyHole c=new CubbyHole();  
  Producer p1 = new Producer(c, 1);    //提供者线程,只运行构造器
  Consumer c1 = new Consumer(c, 1);    //消费者线程,只运行构造器
  
  /**
   * start表示让cpu准备就绪,运行run()的顺序由cpu决定
   * 这里由CubbyHole的available决定,初始化改为true则先运行c1.get方法
   */

  p1.start();  
  c1.start();  
    }
}

    class CubbyHole {
  private int contents;
  
  /**
   * available线程挂起标记,为true表示put挂起,get执行,false则get挂起,put执行
   */

  private boolean available = false;  

  public synchronized int get() {
      while (available == false){
    try{
        wait();
    }
    catch(InterruptedException e){}    
      }
      available = false;
      notifyAll();
      return contents;
  }
  
  /**
   * 先运行put方法,完成之后将put挂起(available = true),若不挂起则永远不会执行get
   * notify和notifyAll是一样的,唤醒等待的线程,若不唤醒则put只执行一次
   * @param value
   */

  public synchronized void put(int value) {
      while (available == true) {
    try{
        wait();
    }
    catch(InterruptedException e){}    
      }
      contents = value;
      available = true;
      notifyAll();      
  }  
    }
  
    class Producer extends Thread{
  private CubbyHole cubbyhole;
  private int number;
  
  public Producer(CubbyHole c, int number) {
      cubbyhole = c;
      this.number = number;
  }
  
  public void run() {
      for (int i = 0; i < 10; i++) {
    cubbyhole.put(i);
    System.out.println("Producer #" + this.number+ " put: " + i);
    try {
        sleep((int)(Math.random() * 100));    //线程等待0.1秒再执行
    }
    catch (InterruptedException e){
        e.printStackTrace();
    }
      }
  }
    }
  
    class Consumer extends Thread {
  private CubbyHole cubbyhole;
  private int number;
  
  public Consumer(CubbyHole c, int number) {
      cubbyhole = c;
      this.number = number;
  }
  
  public void run() {
      int value = 0;
      for (int i = 0; i < 10; i++) {
    value = cubbyhole.get();
    System.out.println("Consumer #" + this.number+ " got: " + value);
      }
  }
    }  
输出结果
Producer #1 put: 0
Consumer #1 got: 0
Producer #1 put: 1
Consumer #1 got: 1
Producer #1 put: 2
Consumer #1 got: 2
Producer #1 put: 3
Consumer #1 got: 3
Producer #1 put: 4
Consumer #1 got: 4
Producer #1 put: 5
Consumer #1 got: 5
Producer #1 put: 6
Consumer #1 got: 6
Producer #1 put: 7
Consumer #1 got: 7
Producer #1 put: 8
Consumer #1 got: 8
Producer #1 put: 9
Consumer #1 got: 9
Servlet线程
        Servlet是在多线程环境下的。即可能有多个请求发给一个servelt实例,每个请求是一个线程。
        1.什么是线程安全的代码
           在多线程环境下能正确执行的代码就是线程安全的。
           安全的意思是能正确执行,否则后果是程序执行错误,可能出现各种异常情况。

        2.如何编写线程安全的代码
          很多书籍里都详细讲解了如何这方面的问题,他们主要讲解的是如何同步线程对共享资源的使用的问题。主要是对synchronized关键字的各种用法,以及锁的概念。
Java1.5中也提供了如读写锁这类的工具类。这些都需要较高的技巧,而且相对难于调试。

           但是,线程同步是不得以的方法,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在编写容易度和性能上会更好些。
我这里强调的是什么代码是始终为线程安全的、是不需要同步的。如下:
          1)常量始终是线程安全的,因为只存在读操作。
          2)对构造器的访问(new 操作)是线程安全的,因为每次都新建一个实例,不会访问共享的资源。
          3)最重要的是:局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量。





    文章评论
 
2008-01-15 13:19:56
来看看。。。。

2008-01-16 09:36:05
博主的文章写的很好,现在正是注重线程编程的时期。支持。!!已经将您的文章推入javaEE博客圈 http://g.51cto.com/javaee

 

发表评论

昵   称:
验证码:  点击图片可刷新验证码  博客过2级,无需填写验证码
内   容: