zoukankan      html  css  js  c++  java
  • 多线程的几种实现方法详解

    [接触多线程]

    class MyThread extends Thread{
     public void run(){
      System.out.println("Thread say:Hello,World!");
     }
    }

    public class MoreThreads{
     public static void main(String[] args){
      new MyThread();
      new MyThread().start();
      System.out.println("Main say:Hello,World");
     }
    }

      执行这个程序,main方法第一行产生了一个线程对象,但并没有线程启动。

      main方法第二行产生了一个线程对象,并启动了一个线程。

      main方法第三行,产生并启动一个线程后,主线程自己也继续执行其它语句。

      我们先不研究Thread对象的具体内容,稍微来回想一下上面的两个概念,线程对象和线程。在JAVA中,线程对象是JVM产生的一个普通的Object子类。而线程是CPU分配给这个对象的一个运行过程。我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。如果一时想不明白,不要急,但你要记得它们不是一回事就行了。

      [线程的并发与并行]

      在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。

      [JAVA线程对象]

      现在我们来开始考察JAVA中线程对象。

      在JAVA中,要开始一个线程,有两种方式。一是直接调用Thread实例的start()方法,二是

      将Runable实例传给一个Thread实例然后调用它的start()方法。

      在前面已经说过,线程对象和线程是两个完全不同的概念。这里我们再次深入一下,生成一个线程的实例,并不代表启动了线程。而启动线程是说在某个线程对象上启动了该实例对应的线程,当该线程结束后,并不会就立即消失。

      对于从很多书籍上可以看到的基础知识我就不用多说了。既然是基础知识,我也着重于从普通文档上读不到的内容。所以本节我重点要说的是两种线程对象产生线程方式的区别。

    class MyThread extends Thread{
     public int x = 0;
     public void run(){
      for(int i=0;i<100;i++){
        try{
         Thread.sleep(10);
        }catch(Exception e){}
        System.out.println(x++);
      }
     }
    }

      如果我们生成MyThread的一个实例,然后调用它的start()方法,那么就产生了这个实例对应的线程:

    public class Test {
     public static void main(String[] args) throws Exception{
      MyThread mt = new MyThread();
      mt.start();
     }
    }

      不用说,最终会打印出0到99,现在我们稍微玩一点花样:

    public class Test {
     public static void main(String[] args) throws Exception{
      MyThread mt = new MyThread();
      mt.start();
      System.out.println(101);
     }
    }

      也不用说,在基础篇(一)中我们知道由于单CPU的原因,一般会先打印101,然后打印0到99。不过我们可以控制线程让它按我们的意思来运行:

    public class Test {
     public static void main(String[] args) throws Exception{
      MyThread mt = new MyThread();
      mt.start();
      mt.join();
      System.out.println(101);
     }
    }

      好了,我们终于看到,mt实例对应的线程(假如我有时说mt线程请你不要怪我,不过我尽量不这么说)。在运行完成后,主线程才打印101。因为我们让当前线程(这里是主线程)等待mt线程的运行结束。"在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后才继续运行。" 请大家一定要深刻理解并熟记这句话,而我这里引出这个知识点的目的是为了让你继续看下面的例子:

    public class Test {
     public static void main(String[] args) throws Exception{
      MyThread mt = new MyThread();
      mt.start();
      mt.join();
      Thread.sleep(3000);
      mt.start();
     }
    }

      当线程对象mt运行完成后,我们让主线程休息一下,然后我们再次在这个线程对象上启动线程。结果我们看到:

      Exception in thread "main" java.lang.IllegalThreadStateException

      也就是这种线程对象一时运行一次完成后,它就再也不能运行第二次了。我们可以看一下它有具体实现:

    public synchronized void start() {
     if (started)
      throw new IllegalThreadStateException();
      started = true;
      group.add(this);
      start0();
     }

      一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了,这意味着:

      [通过Thread实例的start(),一个Thread的实例只能产生一个线程]

      那么如果要在一个实例上产生多个线程(也就是我们常说的线程池),我们应该如何做呢?这就是Runnable接口给我们带来的伟大的功能。

    class R implements Runnable{
     private int x = 0;
     public void run(){
      for(int i=0;i<100;i++){
        try{
         Thread.sleep(10);
        }catch(Exception e){}
        System.out.println(x++);
      }
     }
    }

      正如它的名字一样,Runnable的实例是可运行的,但它自己并不能直接运行,它需要被Thread对象来包装才行运行:

    public class Test {
     public static void main(String[] args) throws Exception{
      new Thread(new R()).start();
     }
    }

      当然这个结果和mt.start()没有什么区别。但如果我们把一个Runnable实例给Thread对象多次包装,我们就可以看到它们实际是在同一实例上启动线程:

    public class Test {
     public static void main(String[] args) throws Exception{
      R r = new R();
      for(int i=0;i<10;i++)
        new Thread(r).start();
     }
    }

      x是实例对象,但结果是x被加到了999,说明这10个线程是在同一个r对象上运行的。请大家注意,因为这个例子是在单CPU上运行的,所以没有对多个线程同时操作共同的对象进行同步。这里是为了说明的方便而简化了同步,而真正的环境中你无法预知程序会在什么环境下运行,所以一定要考虑同步。

      到这里我们做一个完整的例子来说明线程产生的方式不同而生成的线程的区别:

    package debug;
    import java.io.*;
    import java.lang.Thread;
    class MyThread extends Thread{
     public int x = 0;
     public void run(){
      System.out.println(++x);
     }
    }

    class R implements Runnable{
     private int x = 0;
     public void run(){
      System.out.println(++x);
     }
    }
    public class Test {
     public static void main(String[] args) throws Exception{
      for(int i=0;i<10;i++){
        Thread t = new MyThread();
        t.start();
      }
      Thread.sleep(10000);//让上面的线程运行完成
      R r = new R();
      for(int i=0;i<10;i++){
        Thread t = new Thread(r);
        t.start();
      }
     }  
    }

      上面10个线程对象产生的10个线程运行时打印了10次1。下面10个线程对象产生的10个线程运行时打印了1到10。我们把下面的10个线程称为同一实例(Runnable实例)的多个线程。

  • 相关阅读:
    三个心态做人做学问 沧海
    成功走职场要找准自己的"快捷键" 沧海
    免费离线下载 拂晓风起
    Hibernate 获取某个表全部记录时 奇怪现象 (重复出现某个记录) 拂晓风起
    无法读取mdb 如果连接不了ACCESS mdb文件,就尝试安装MDAC 拂晓风起
    Netbeans 使用 Hibernate 逆向工程 生成hbm和pojo 拂晓风起
    如何点击单选框 radio 后面的文字,选中单选框 拂晓风起
    Java 连接access 使用access文件 不用配置 拂晓风起
    mysql下如何执行sql脚本 拂晓风起
    Hibernate配置access Hibernate 连接 access 拂晓风起
  • 原文地址:https://www.cnblogs.com/cyl048/p/8506383.html
Copyright © 2011-2022 走看看