惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
NISL@THU
NISL@THU
E
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
K
Kaspersky official blog
Project Zero
Project Zero
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
S
Schneier on Security
G
GRAHAM CLULEY
The Hacker News
The Hacker News
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Cyberwarzone
Cyberwarzone
C
CERT Recently Published Vulnerability Notes
T
Tor Project blog
AWS News Blog
AWS News Blog
Simon Willison's Weblog
Simon Willison's Weblog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
爱范儿
爱范儿
P
Privacy International News Feed
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
S
Securelist
G
Google Developers Blog
The Last Watchdog
The Last Watchdog
Google Online Security Blog
Google Online Security Blog
美团技术团队
F
Fortinet All Blogs
小众软件
小众软件
Recorded Future
Recorded Future
V
Visual Studio Blog
B
Blog RSS Feed
H
Help Net Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
博客园 - 聂微东
Stack Overflow Blog
Stack Overflow Blog
Martin Fowler
Martin Fowler
Latest news
Latest news
Spread Privacy
Spread Privacy
H
Heimdal Security Blog

博客园 - frank.net

中国软件工业的冤枉路 随便写写 windows2000实现双机热备份(转) 规范软件开发过程-软件配置管理实践 多线程编程 高级主题(二)注:转 多线程编程 高级主题(一)注:转 多线程编程 秘籍篇(二)注:转 多线程编程 秘籍篇(一)注:转 多线程编程 实践篇(五)注:转 - frank.net - 博客园 多线程编程 实践篇(四)注:转 - frank.net - 博客园 多线程编程 实践篇(三)注:转 多线程编程 实践篇(二)注:转 多线程编程 实践篇(一)注:转 多线程编程 基础篇(四)注:转 多线程编程 基础篇(三)注:转 多线程编程 基础篇(一)注:转 多线程编程 关于创业方向和项目 Servlet Cookie操作总结
多线程编程 基础篇(二)注:转
frank.net · 2008-10-07 · via 博客园 - frank.net

在进入java平台的线程对象之前,基于基础知识(一)的一些问题,我先插入两个基本概念.

[线程的并发与并行]

在单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实例)的多个线程.