`
yant
  • 浏览: 18662 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

线程概述(转)

阅读更多
编写具有多线程能力的程序经常会用到的方法有:

      run(), start(), wait(), notify(), notifyAll(), sleep(), yield(), join()

      还有一个重要的关键字:synchronized

      本文将对以上内容进行讲解。

      一:run() 和start()

      示例1:

    public class ThreadTest extends Thread {
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.print(" " + i);
    }
  }

  public static void main(String[] args) {
    new ThreadTest().start();
    new ThreadTest().start();
  }
}

  这是个简单的多线程程序。run() 和start() 是大家都很熟悉的两个方法。把希望并行处理的代码都放在run() 中;stat() 用于自动调用run(),
    这是JAVA的内在机制规定的。并且run() 的访问控制符必须是public,返回值必须是void(这种说法不准确,run() 没有返回值),run()
    不带参数。

      这些规定想必大家都早已知道了,但你是否清楚为什么run方法必须声明成这样的形式?这涉及到JAVA的方法覆盖和重载的规定。这些内容很重要,
    请读者参考相关资料。

      二:关键字synchronized

      有了synchronized关键字,多线程程序的运行结果将变得可以控制。synchronized关键字用于保护共享数据。请大家注意 "共享数据",
    你一定要分清哪些数据是共享数据,JAVA是面向对象的程序设计语言,所以初学者在编写多线程程序时,容易分不清哪些数据是共享数据。请看下面的例子:

      示例2:

    public class ThreadTest implements Runnable {

  public synchronized void run() {
    for (int i = 0; i < 10; i++) {
      System.out.print(" " + i);
    }
  }

  public static void main(String[] args) {
    Runnable r1 = new ThreadTest();
    Runnable r2 = new ThreadTest();
    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);
    t1.start();
    t2.start();
  }
}

  在这个程序中,run() 被加上了synchronized关键字。在main方法中创建了两个线程。你可能会认为此程序的运行结果一定为:0123456789
    0123456789。但你错了!这个程序中synchronized关键字保护的不是共享数据(
    其实在这个程序中synchronized关键字没有起到任何作用,此程序的运行结果是不可预先确定的)。这个程序中的t1, t2是两个对象(r1,
    r2)的线程。JAVA是面向对象的程序设计语言,不同的对象的数据是不同的,r1,
    r2有各自的run() 方法,而synchronized使同一个对象的多个线程,
    在某个时刻只有其中的一个线程可以访问这个对象的synchronized数据。每个对象都有一个 "锁标志",
    当这个对象的一个线程访问这个对象的某个synchronized数据时,这个对象的所有被synchronized修饰的数据将被上锁(因为 "锁标志"
    被当前线程拿走了),只有当前线程访问完它要访问的synchronized数据时,当前线程才会释放 "锁标志",
    这样同一个对象的其它线程才有机会访问synchronized数据。

      示例3:

    public class ThreadTest implements Runnable {
  public synchronized void run() {
    for (int i = 0; i < 10; i++) {
      System.out.print(" " + i);
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);
    t1.start();

    t2.start();
  }
}

  如果你运行1000次这个程序,它的输出结果也一定每次都是:01234567890123456789。因为这里的synchronized保护的是共享数据。
    t1,
    t2是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run() 方法时,由于run() 受synchronized保护,所以同一个对象的其他线程(
    t2)无法访问synchronized方法(run方法)。只有当t1执行完后t2才有机会执行。

      示例4:

    public class ThreadTest implements Runnable {
  public void run() {
    synchronized (this) {
      for (int i = 0; i < 10; i++) {
        System.out.print(" " + i);
      }
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);
    t1.start();
    t2.start();
  }
}

  这个程序与示例3的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4的形式,this代表 "这个对象"。没有必要把整个run() 保护起来,
    run() 中的代码只有一个for循环,所以只要保护for循环就可以了。

      示例5:

    public class ThreadTest implements Runnable {
  public void run() {
    for (int k = 0; k < 5; k++) {
      System.out.println(Thread.currentThread().getName()
                         + " : for loop : " + k);

    }
    synchronized (this) {
      for (int k = 0; k < 5; k++) {
        System.out.println(Thread.currentThread().getName()
                           + " : synchronized for loop : " + k);
      }
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r, "t1_name");
    Thread t2 = new Thread(r, "t2_name");
    t1.start();
    t2.start();
  }
}

运行结果:t1_name : for loop : 0
    t1_name : for loop : 1
    t1_name : for loop : 2
    t2_name : for loop : 0
    t1_name : for loop : 3
    t2_name : for loop : 1
    t1_name : for loop : 4
    t2_name : for loop : 2
    t1_name : synchronized for loop : 0
    t2_name : for loop : 3
    t1_name : synchronized for loop : 1
    t2_name : for loop : 4
    t1_name : synchronized for loop : 2
    t1_name : synchronized for loop : 3
    t1_name : synchronized for loop : 4
    t2_name : synchronized for loop : 0
    t2_name : synchronized for loop : 1
    t2_name : synchronized for loop : 2
    t2_name : synchronized for loop : 3
    t2_name : synchronized for loop : 4

      第一个for循环没有受synchronized保护。对于第一个for循环,t1,
    t2可以同时访问。运行结果表明t1执行到了k = 2时,t2开始执行了。t1首先执行完了第一个for循环,此时还没有执行完第一个for循环(
    t2刚执行到k = 2)。t1开始执行第二个for循环,当t1的第二个for循环执行到k = 1时,t2的第一个for循环执行完了。
    t2想开始执行第二个for循环,但由于t1首先执行了第二个for循环,这个对象的锁标志自然在t1手中(
    synchronized方法的执行权也就落到了t1手中),在t1没执行完第二个for循环的时候,它是不会释放锁标志的。
    所以t2必须等到t1执行完第二个for循环后,它才可以执行第二个for循环

三:sleep()

      示例6:

    public class ThreadTest implements Runnable {
  public void run() {
    for (int k = 0; k < 5; k++) {
      if (k == 2) {
        try {
          Thread.currentThread().sleep(5000);
        }
        catch (Exception e) {}
      }
      System.out.print(" " + k);
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t = new Thread(r);
    t.start();
  }
}

  sleep方法会使当前的线程暂停执行一定时间(给其它线程运行机会)。读者可以运行示例6,看看结果就明白了。sleep方法会抛出异常,必须提供捕获代码。

      示例7:

    public class ThreadTest implements Runnable {
  public void run() {

    for (int k = 0; k < 5; k++) {
      if (k == 2) {
        try {
          Thread.currentThread().sleep(5000);
        }
        catch (Exception e) {}
      }
      System.out.println(Thread.currentThread().getName()
                         + " : " + k);
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r, "t1_name");
    Thread t2 = new Thread(r, "t2_name");
    t1.setPriority(Thread.MAX_PRIORITY);
    t2.setPriority(Thread.MIN_PRIORITY);
    t1.start();
    t2.start();
  }
}

  t1被设置了最高的优先级,t2被设置了最低的优先级。t1不执行完,t2就没有机会执行。但由于t1在执行的中途休息了5秒中,这使得t2就有机会执行了。
    读者可以运行这个程序试试看。

      示例8:

    public class ThreadTest implements Runnable {
  public synchronized void run() {
    for (int k = 0; k < 5; k++) {
      if (k == 2) {
        try {
          Thread.currentThread().sleep(5000);
        }
        catch (Exception e) {}

      }
      System.out.println(Thread.currentThread().getName()
                         + " : " + k);
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r, "t1_name");
    Thread t2 = new Thread(r, "t2_name");
    t1.start();
    t2.start();
  }
}

  请读者首先运行示例8程序,从运行结果上看:一个线程在sleep的时候,并不会释放这个对象的锁标志。

      四:join()

      示例9:

    public class ThreadTest implements Runnable {
  public static int a = 0;
  public void run() {
    for (int k = 0; k < 5; k++) {
      a = a + 1;
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t = new Thread(r);
    t.start();
    System.out.println(a);
  }
}

  请问程序的输出结果是5吗?答案是:有可能。其实你很难遇到输出5的时候,通常情况下都不是5。这里不讲解为什么输出结果不是5,我要讲的是:
    怎样才能让输出结果为5!其实很简单,join() 方法提供了这种功能。join() 方法,它能够使调用该方法的线程在此之前执行完毕。

      把示例9的main() 方法该成如下这样:

    public static void main(String[] args) throws Exception {
  Runnable r = new ThreadTest();
  Thread t = new Thread(r);
  t.start();
  t.join();
  System.out.println(a);
}

  这时,输出结果肯定是5!join() 方法会抛出异常,应该提供捕获代码。或留给JDK捕获。

      示例10:

    public class ThreadTest implements Runnable {
  public void run() {
    for (int k = 0; k < 10; k++) {
      System.out.print(" " + k);
    }
  }

  public static void main(String[] args) throws Exception {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);
    t1.start();
    t1.join();
    t2.start();
  }
}

  运行这个程序,看看结果是否与示例3一样

五:yield()

      yield() 方法与sleep() 方法相似,只是它不能由用户指定线程暂停多长时间。按照SUN的说法:
    sleep方法可以使低优先级的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会。而yield()
    方法只能使同优先级的线程有执行的机会。

      示例11:

    public class ThreadTest implements Runnable {
  public void run() {
    8
        for (int k = 0; k < 10; k++) {
      if (k == 5 && Thread.currentThread().getName().equals("t1")) {
        Thread.yield();
      }
      System.out.println(Thread.currentThread().getName()
                         + " : " + k);
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r, "t1");
    Thread t2 = new Thread(r, "t2");
    t1.setPriority(Thread.MAX_PRIORITY);
    t2.setPriority(Thread.MIN_PRIORITY);
    t1.start();
    t2.start();
  }
}

输出结果:
    t1 : 0
    t1 : 1
    t1 : 2
    t1 : 3
    t1 : 4
    t1 : 5
    t1 : 6
    t1 : 7
    t1 : 8
    t1 : 9
    t2 : 0
    t2 : 1
    t2 : 2
    t2 : 3
    t2 : 4
    t2 : 5
    t2 : 6
    t2 : 7
    t2 : 8
    t2 : 9

      多次运行这个程序,输出也是一样。这说明:yield() 方法不会使不同优先级的线程有执行的机会。

      六:wait(), notify(), notifyAll()

      首先说明:wait(), notify(),
    notifyAll() 这些方法由java.lang.Object类提供,而上面讲到的方法都是由java.lang.Thread类提供(
    Thread类实现了Runnable接口)。

      wait(), notify(),
    notifyAll() 这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用这三个方法。先看下面了例子:

      示例12:

    public class ThreadTest implements Runnable {
  public static int shareVar = 0;
  public synchronized void run() {
    if (shareVar == 0) {
      for (int i = 0; i < 10; i++) {
        shareVar++;
        if (shareVar == 5) {
          try {
            this.wait();
          }
          catch (Exception e) {}
        }
      }
    }
    if (shareVar != 0) {
      System.out.print(Thread.currentThread().getName());
      System.out.println(" shareVar = " + shareVar);
      this.notify();
    }
  }

  public static void main(String[] args) {
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r, "t1");
    10
        Thread t2 = new Thread(r, "t2");
    t1.start();
    t2.start();
  }
}

运行结果:
    t2 shareVar = 5
    t1 shareVar = 10

      t1线程最先执行。由于初始状态下shareVar为0,t1将使shareVar连续加1,当shareVar的值为5时,t1调用wait() 方法,
    t1将处于休息状态,同时释放锁标志。这时t2得到了锁标志开始执行,shareVar的值已经变为5,所以t2直接输出shareVar的值,
    然后再调用notify() 方法唤醒t1。t1接着上次休息前的进度继续执行,把shareVar的值一直加到10,由于此刻shareVar的值不为0,
    所以t1将输出此刻shareVar的值,然后再调用notify() 方法,由于此刻已经没有等待锁标志的线程,所以此调用语句不起任何作用。

      这个程序简单的示范了wait(), notify() 的用法,读者还需要在实践中继续摸索。

      七:关于线程的补充

      编写一个具有多线程能力的程序可以继承Thread类,也可以实现Runnable接口。在这两个方法中如何选择呢?从面向对象的角度考虑,
    作者建议你实现Runnable接口。有时你也必须实现Runnable接口,例如当你编写具有多线程能力的小应用程序的时候。

      线程的调度:NewRunningRunnableOtherwise BlockedDeadBlocked in object`sit()
    poolBlocked in object`slock poolnotify() Schedulercompletesrun() start()
    sleep() or join() sleep() timeout or thread join() s or interupt()
    Lockavailablesynchronized() Thread states

      terupt() 一个Thread对象在它的生命周期中会处于各种不同的状态,上图形象地说明了这点。wa in

      调用start() 方法使线程处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。

      实际上,程序中的多个线程并不是同时执行的。除非线程正在真正的多CPU计算机系统上执行,否则线程使用单CPU必须轮流执行。但是,由于这发生的很快,
    我们常常认为这些线程是同时执行的。

      JAVA运行时系统的计划调度程序是抢占性的。如果计划调度程序正在运行一个线程并且来了另一个优先级更高的线程,
    那么当前正在执行的线程就被暂时终止而让更高优先级的线程执行。

      JAVA计划调度程序不会为与当前线程具有同样优先级的另一个线程去抢占当前的线程。但是,尽管计划调度程序本身没有时间片(
    即它没有给相同优先级的线程以执行用的时间片),但以Thread类为基础的线程的系统实现可能会支持时间片分配。这依赖具体的操作系统,
    Windows与UNIX在这个问题上的支持不会完全一样。

      由于你不能肯定小应用程序将运行在什么操作系统上,因此你不应该编写出依赖时间片分配的程序。就是说,
    应该使用yield方法以允许相同优先级的线程有机会执行而不是希望每一个线程都自动得到一段CPU时间片。

      Thread类提供给你与系统无关的处理线程的机制。但是,线程的实际实现取决于JAVA运行所在的操作系统。因此,
    线程化的程序确实是利用了支持线程的操作系统。

      当创建线程时,可以赋予它优先级。它的优先级越高,它就越能影响运行系统。
    JAVA运行系统使用一个负责在所有执行JAVA程序内运行所有存在的计划调度程序。
    该计划调度程序实际上使用一个固定优先级的算法来保证每个程序中的最高优先级的线程得到CPU--允许最高优先级的线程在其它线程之前执行。

      对于在一个程序中有几个相同优先级的线程等待执行的情况,该计划调度程序循环地选择它们,当进行下一次选择时选择前面没有执行的线程,
    具有相同优先级的所有的线程都受到平等的对待。较低优先级的线程在较高优先级的线程已经死亡或者进入不可执行状态之后才能执行。

      继续讨论wait(), notify(), notifyAll():

      当线程执行了对一个特定对象的wait() 调用时,那个线程被放到与那个对象相关的等待池中。此外,调用wait() 的线程自动释放对象的锁标志。

      可以调用不同的wait():wait() 或wait(long timeout)

      对一个特定对象执行notify() 调用时,将从对象的等待池中移走一个任意的线程,并放到锁标志等待池中,那里的线程一直在等待,
    直到可以获得对象的锁标志。notifyAll() 方法将从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
    只有锁标志等待池中的线程能获取对象的锁标志,锁标志允许线程从上次因调用wait() 而中断的地方开始继续运行。

      在许多实现了wait() / notify() 机制的系统中,醒来的线程必定是那个等待时间最长的线程。然而,在Java技术中,并不保证这点。

      注意,不管是否有线程在等待,都可以调用notify()。如果对一个对象调用notify() 方法,而在这个对象的锁标志等待池中并没有线程,
    那么notify() 调用将不起任何作用。

      在JAVA中,多线程是一个神奇的主题。之所以说它 "神奇",是因为多线程程序的运行结果不可预测,但我们又可以通过某些方法控制多线程程序的执行。
    要想灵活使用多线程,读者还需要大量实践。

      另外,从JDK 1.2开始,SUN就不建议使用resume(), stop(), suspend() 了



本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/doc123/archive/2008/09/20/2950610.aspx
分享到:
评论

相关推荐

    6线程概述.xmind

    6线程概述.xmind

    6线程概述.png

    6线程概述.png

    MFC多线程编程多线程概述

    MFC多线程编程多线程概述  进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所...

    CVI多线程概述

    cvi的多线程讲解,有助于理解CVI下的多线程工作原理及其实现方法

    VC多线程编程概述.pdf

    VC多线程编程(转) VC中多线程使用比较广泛而且实用,在网上看到的教程.感觉写的挺好.

    多线程(概述)

    多线程,非常好的多线程教材,可以为多线程编程提供帮助。

    linux多线程编程概述.doc

    linux多线程编程概述,入门手册.浅显易懂.

    MFC线程处理概述.doc

    MFC多线程编程方面的资料,希望对大家有用; 彻底共享,决不要分!

    java 多线程设计模式 进程详解

    线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口的线程 线程的生命周期 线程命名 访问线程 线程的启动、停止和连接 总结 第三章 同步技术 银行的例子 异步读取数据 ...

    线程进程安全概述.pptx

    线程进程安全概述.pptx

    054集-线程通信概述

    054集-线程通信概述视频教程,很是不错哦,有兴趣的可以学习下。

    多线程学习资源

    多线程概述,past,coolie,多线程控制,多线程验证码

    java并发编程:线程基础

    多线程概述: 简要介绍多线程编程的重要性和应用场景。解释为什么多线程可以提高程序性能和资源利用率。 线程的创建: 详细讲解线程的创建方式,包括继承 Thread 类和实现 Runnable 接口。演示如何通过这些方式来...

    运行时数据区概述及线程.xmind

    自己总结的jvm中运行时数据区概述及线程的笔记,绘制了详细的思维导图,每个思维导图中均有详细的博文解释,方便大家学习和理解,免费分享给大家。适合jvm的爱好者和学习者

    Solaris多线程编程指南

    第1 章概述本发行版中线程实现的结构。 第2 章讨论常规POSIX 线程例程,其中重点介绍如何创建具有缺省属性的线程。 第3 章介绍如何创建具有非缺省属性的线程。 第4 章介绍线程同步例程。 第5 章讨论为支持多线程而对...

    基于C语言实现的C语言线程库(含源码)

    1. 线程概述 2. 创建线程 2.1 线程函数 2.2 创建线程 3. 线程退出 4. 线程回收 4.1 线程函数 4.2 回收子线程数据 5. 线程分离 6. 其他线程函数 6.1 线程取消 6.2 线程 ID 比较

    java 线程 dump 分析工具 2.3.3

    java 线程Dump 分析工具: Java的TDA线程转储分析器是一个用于分析Sun Java VM生成的线程转储和堆信息的小型Swing GUI(目前用1.4测试)。...如果记录了类的直方图,它还提供了线程转储中的堆对象的概述。

    Windows多线程编程(pdf)

    二、多线程概述 2 三、Win32 API对多线程编程的支持 3 四、Win32 API多线程编程例程 4 例程1 MultiThread1 4 例程2 MultiThread2 5 例程3 MultiThread3 6 例程4 MultiThread4 9 多线程编程之二——MFC中的多线程开发...

    Java程序基础-多线程概述.pptx

    java

    vc编程之多线程详解

    一、问题的提出 二、多线程概述 三、Win32 API对多线程编程的支持 四、Win32 API多线程编程例程 五、MFC对多线程编程的支持 六、MFC多线程编程实例 七、线程间通讯 八、线程的同步

Global site tag (gtag.js) - Google Analytics