Java 里的thread (线程)简介

Java里 thread 就是线程的意思.

  说到线程的概念, 自然离不开另外两个词: 程序和进程.

  从最基本的程序讲起:

  一. 什么是程序(Program)

  所谓程序, 就是1个严格有序的指令集合. 程序规定了完成某一任务时,计算机所需要做的各种操作, 以及操作的顺序.

  1.1 单道程序运行环境

  所谓单道程序环境就是指, 计算机除了操作系统之外, 只允许运行1个用户程序.

  以前的DOS系统就是1个典型的单道程序运行环境.

  单道程序有如下特点:

  1. 资源的独占性: 任何时候, 内存内只有1个用户程序, 这个程序独享系统的所有资源.

  2. 执行顺序性: 内存中只执行1个程序, 各程序是按次序执行的, 即是做完1件事后, 才做另一件事.

  绝不可能执行1个程序的中途暂停, 然后去执行另1个程序.

  3.结果的再现性: 只要执行环境和初始条件相同, 重复执行1个程序, 获得的结果总是一样的.

  1.2 多道程序运行环境

  所谓多道程序运行环境是指, 计算机除了操作系统之外, 允许同时运行多个用户程序.

  当今的unix, linux都是典型的多道程序运行环境.

  多道程序有如下特点:

  1. 间断性: 也就是cpu会在多个程序之间切换执行, 例如cpu执行程序A一段时间后, 会切换去执行程序B一段时间.

  由于这个时间段很短, 所以对用户造成1个程序A和程序B同时执行的假象.

  2. 失去封闭性:  程序执行受外界影响, 也就是多个程序之间共享资源, 互相制约.

  3. 不可再现性:  重复执行时, 可能得到不同的结果. 原因就是上面的点, 执行时可能会受到内存中其他程序的影响.

  二. 什么是进程(process)

  进程这个概念是基于多道程序运行环境来讲的.

  1个进程就是程序在内存中运行的1个实例

  由于在多道程序运行环境中, 同1个程序可以同时产生两个实例在内存里运行.

  举个简单例子:  例如你可以在操作系统打开两个gvim 文本编辑器, 同时编辑两个不同的文件.

  这时执行ps -ef | grep gvim 就会见到系统中有两个名字是gvim的进程, 但是它们的进程id是不同的.

  也就是将,  gvim这个程序在内存中生成两个进程同时运行.

  在多道程序运行环境中.  引用进程这个概念来描叙程序在内存里事例, 用来区别于程序本身.

  真正深入了解进程并不简单, 进程其实算是属于操作系统这门课的范畴. 1个进程包括程序段, 数据段, 程序控制块等,  但是作为一个普通的程序猿, 没必要理解得这么深入.

  三. 什么是线程(thread).

  相对地, 我认为作为1个普通的程序猿, 必须深入了解线程这个概念.

  其实, 线程就是1个进程的执行路径.

  一般的java程序都是从启动类的main函数入口开始执行, main函数的结束而停止.   这条执行路径就是java程序的主线程.

  也就是讲:

  线程是相对于进程来讲的, 1个进程至少存在1条线程.

  而java允许多线程编程, 也就是1个进程除主线程外,  还允许存在1个或若干个其他线程, cpu会在这些线程当中间断切换执行, 给用户造成同时执行的假象.

  四. 1个单线程java程序的简单例子.

  例子如下:


package Thread_kng;

class S_thrd_1{

public void f(){

while (true){

System.out.printf("Thread main is runing!\n");

}

//System.out.printf("f() is done!\n");  //compilation fail

}

}

public class S_thread_expl{

public static void g(){

S_thrd_1 s = new S_thrd_1();

s.f();

System.out.printf("g() is done!\n");

}

}

 上面的例子中,  g() 函数调用了f() 函数,

  g()函数在最后尝试输出"g() is done!" , 但是因为f() 是一个无限循环.  所以g() 调用f()后, 根本没机会执行后面的语句!

  也就是说, 虽然g() 跟 f() 是两个不同类的函数, 但是它们是在程序中的同一个线程(主线程)内执行.

  在同一个线程内, 语句总是按程序猿编写的顺序依次执行, 一条语句未执行完时, 不会跳到后面执行其他的语句.

  五. 1个多线程java程序的简单例子.

  例子如下:


package Thread_kng;

class M_thrd_1 extends Thread{  //extends

public M_thrd_1(String name){

super(name);

}

public void run(){ //overwrite the method run() of superclass

while (true){

System.out.printf("Thread " + this.getName()+ " is runing!\n");

}

//System.out.printf("f() is done!\n");  //compilation fail

}

}

public class M_thread_expl{

public static void g(){

M_thrd_1 s = new M_thrd_1("T1");

s.start(); //start()method is extended from superclass, it will call the method run()

M_thrd_1 s2 = new M_thrd_1("T2");

s2.start();

System.out.printf("g() is done!\n");

}

  上面例子中, 类 M_thrd_1继承了线程类Thread.  并重写了run方法.

  在下面的g()函数中.

  实例化了两个类M_thrd_1的对象s, s1 的对象.

  执行了start()方法.

  start()方法继承字Thread, 它会启动1新线程, 并调用run()函数.

  而g()后面本身也在不断循环输出"THread Main is running"

  所以输出如下:

  可以 见到, 输出结果是T1,T2 和Main 交替输出在屏幕上.

  实际上, 这个程序有3个线程.

  其中1个就是主线程.

  主线程main通过实例化两个M_thrd_1的对象, 调用其start()函数开启了两个子线程.

  其中1个子线程不断输出T1

  另1个子线程不断输出T2

  但是主线程并不会等待其子线程执行完成, 会继续执行下面的代码,所以不断循环输出Main!

  所以这个例子实际上要3个线程在输出信息到屏幕了!

  六. Java 开启一条新进程的两种方式.

  java开启一条新线程, 有两种方式. 但是都要利用java的线程基类Thread.

  6.1 方式1. 创建Thread的派生类

  这个就类似上面的例子:

  具体步骤如下:

  6.1.1 线程准备: 新建1个类, 继承线程类Thread.  将业务代码写入重写后的run() 方法中.

  例如上面的例子中的M_thrd_1类


class M_thrd_1 extends Thread{  //extends

public M_thrd_1(String name){

super(name);

}

public void run(){ //overwrite the method run() of superclass

while (true){

System.out.printf("Thread " + this.getName()+ " is runing!\n");

}

//System.out.printf("f() is done!\n");  //compilation fail

}

}

  可以见到, 我们将线程要执行的业务代码写入了run() 方法.

  6.1.2 启动线程: 新建1个上述类的对象, 运行start()方法

  例如

  M_thrd_1 s = new M_thrd();

  s. start();

  其中start() 是类M_thrd_1 继承自超类Thread的.

  我们看看JDK API 对start() 方法的定义:

  void  start()

  使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

  可以见到, 调用了start() 方法后, 会开始执行改线程, 也就是在当前线程下开启一条新线程.

  而这条新线程做什么呢, 就是执行run()里面的代码.

  所以我们一般只需要把也业务的代码写入run()里面就ok了. 不要重写start()方法.

  注意, 如果直接执行s.run(); 则会在当前线程下执行run里面的代码, 并没有开启一条新线程. 区别很大的.

  6.2 方式2. 创建1个类, 实现Runnable 接口

  步骤跟上面的方式差别不大

  6.2.1 线程准备: 新建1个类, 实现接口Runnable. 将业务代码写入重写后的run() 方法中.

  例子:


class M_thrd_2 implements Runnable{  //extends

public int id = 0;

public void run(){ //overwrite the method run() of superclass

while (true){

System.out.printf("%s: Thread " + this.id+ " is runing!\n", Thread.currentThread().getName());

}

//System.out.printf("f() is done!\n");  //compilation fail

}

}

  上面的例子 M_thrd_2 就实现了接口Runnable

  并重写了接口Runnable 的抽象方法run();

  注意.  currentThread() 是类Thread的一个静态方法, 作用是获取当前语句所在的线程.

  6.2.2 启动线程: 新建1个上述类的对象s, 在新建1个类Thread的对象t, 把s作为一个参数用于t的构造方法.  并执行t.start()

  貌似比方式一复杂.

  其实非如下:

  new Thread(new M_thrd_2()).start()

  例子:


public class M_thread_expl2{

public static void g(){

M_thrd_2 s = new M_thrd_2();

s.id = 1;

Thread t1 = new Thread(s);

t1.start();

Thread t2 = new Thread(s);

s.id = 2;

t2.start();

}

}

  在g() 函数中,

  首先新建1个类M_thrd_2的 对象s.

  并利用s作为参数 建立了两个线程类Thread的对象t1 和 t2. 并启动这两个线程.

  注:

  1. 这个例子中有3个线程, 其中1个主线程(也就是g() 函数所在的线程). 开启了两个子线程, 该两个子线程都在不断循环输出信息到屏幕上.

  2. Thread(object ob) 是1个属于类Thread的构造方法,  其中的对象ob 必须实现Runnable 接口.

  3. 这个例子执行时 , 第一个t1首先会输出id = 1,    但是当第二个线程t2开始执行后, t1会输出id=2,  因为t1, 和t2都是利用同1个对象建立的.

  也就是说, 这个对象的变动会同时影响两个线程.

  输出如下:

  6.3 两种方式的区别

  1.  方式1是创建继承类Thread的派生类,  方式2是创建实现Runnable接口的类.

  2.  启动方式:  方式1是直接调用业务类的对象的start()方法, 方式2是利用业务类类的对象新建1个类Thread的对象, 并调用该对象的start()方法.

  3.  如果要启动多个线程, 通过方式1需要新建多个不同业务类的对象, 方式2 则可以通过业务1个对象 构建多个Thread类对象, 但是业务对象的变动会同时影响对应的多个线程.

  七. 关于线程的一些常用方法介绍.

  7.1 run()

  我们要吧线把要执行的业务代码写入这个方法. 上面提过了.  注意, 如果我们直接执行1个线程对象的run()方法可以合法的, 但是仍然是在当前线程内执行, 并没有开始一条新线程.

  7.2 Start()

  当1个线程or其派生类执行1个start()方法. 就开启1个新线程, 并调用该类的run()方法.

  注意: 1个线程如果已经调用过一次start(), 再调用start()则或抛异常.

  7.3 stop()

  停止1个1个线程.

  7.4 setName() 和 getName()

  设置和获得对应线程的名字.

  7.5 Thread.currentThread()

  注意这个方法是一个static方法,  而上面的都不是静态方法.

  这个方法返回当前执行语句所属的线程.

  例如1个线程对象A.    它的run() 方法里调用了 Thread.currentThread().getName() 函数.

  假如直接执行A.run()  则返回的是当前线程(而不是A线程) 的名字,  因为对象A并没有启动自己的线程.

  假如执行难过A.start(), 那么该函数就返回A启动的线程的名字了.

  八, 小结

  这篇文章只是简介, 很多重要的方法例如 sleep() , wait() 等都没有介绍, 这些是线程控制的内容. 本吊打算在另一篇博文里介绍.

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-09-17 08:22:16

Java 里的thread (线程)简介的相关文章

java线程简介 -共享对数据的访问

共享变量 要使多个线程在一个程序中有用,它们必须有某种方法可以互相通信或共享它们的结果. 让线程共享其结果的最简单方法是使用共享变量.它们还应该使用同步来确保值从一个线程正确传播到另一个线程,以及防止当一个线程正在更新一些相关数据项时,另一个线程看到不一致的中间结果. 线程基础中计算素数的示例使用了一个共享布尔变量,用于表示指定的时间段已经过去了.这说明了在线程间共享数据最简单的形式是:轮询共享变量以查看另一个线程是否已经完成执行某项任务. 存在于同一个内存空间中的所有线程 正如前面讨论过的,线

Java基础-创建Java程序中的线程池

程序|创建 线程是Java的一大特性,它可以是给定的指令序列.给定的方法中定义的变量或者一些共享数据(类一级的变量).在Java中每个线程有自己的堆栈和程序计数器(PC),其中堆栈是用来跟踪线程的上下文(上下文是当线程执行到某处时,当前的局部变量的值),而程序计数器则用来跟踪当前线程正在执行的指令. 在通常情况下,一个线程不能访问另外一个线程的堆栈变量,而且这个线程必须处于如下状态之一: 1.排队状态(Ready),在用户创建了一个线程以后,这个线程不会立即运行.当线程中的方法start()被调

java内存模型与线程(转) good

java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm  http://developer.51cto.com/art/201309/410971_all.htm http://www.cnblogs.com/skywang12345/p/3447546.html 计算机的CPU计算能力超强,其计算速度与 内存等存储 和通讯子系统的速度相比快了几个数量级, 数据加载到内存中后,cpu处理器运算处理时,大部分时间花在等待获取去获取磁盘IO.网络

深入解析Java并发程序中线程的同步与线程锁的使用_java

synchronized关键字 synchronized,我们谓之锁,主要用来给方法.代码块加锁.当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码.当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段.但是,其余线程是可以访问该对象中的非加锁代码块的. synchronized主要包括两种方法:synchronized 方法.synchronized 块. synchron

Java多线程之中断线程(Interrupt)的使用详解_java

interrupt方法 interrupt字面上是中断的意思,但在Java里Thread.interrupt()方法实际上通过某种方式通知线程,并不会直接中止该线程.具体做什么事情由写代码的人决定,通常我们会中止该线程.     如果线程在调用Object类的wait().wait(long)或wait(long, int)方法,或者该类的 join() .join(long) .join(long, int) .sleep(long) 或 sleep(long, int) 方法过程中受阻,则其

Java里volatile关键字是什么意思_java

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制. synchronized 同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法 或者 代码块.

Java 6中的线程优化真的有效么?

介绍 - Java 6中的线程优化 Sun.IBM.BEA和其他公司在各自实现的Java 6虚拟机上都花费了大量的精力 优化锁的管理和同步.诸如偏向锁(biased locking).锁粗化(lock coarsening).由逸出(escape)分析产生的锁省略.自适应自旋锁(adaptive spinning)这些特性,都是通过在应用程序线程之间更高效地共享数据,从而提 高并发效率.尽管这些特性都是成熟且有趣的,但是问题在于:它们的承诺真的 能实现么?在这篇由两部分组成的文章里,我将逐一探究

多线程问题-新手求助关于Java多线程中启动线程问题

问题描述 新手求助关于Java多线程中启动线程问题 public class Example15_1 { /** * @param args */public static void main(String[] args) { // TODO Auto-generated method stub SpeakHello speakHello; SpeakNinhao speakNinhao; speakHello = new SpeakHello(); speakNinhao= new Speak

java中panel实现线程接口以后,要调用repaint函数时,不进run函数

问题描述 java中panel实现线程接口以后,要调用repaint函数时,不进run函数 具体情况是 mypanel类实现了线程接口,在run函数中定义了sleep(100)后调用repaint函数,做一个小坦克游戏,在repaint之前要判断是否击中坦克,击中后要显示三张图片来体现爆炸效果,可是经过调试发现,每次第一次击中的时候,都是直接好多次repaint,没有休眠,后来发现根本就没有进mypanel的run()方法,好像有另一个其他线程再调用paint.这是怎么回事?感谢大家了 pack