您的位置:首页 > 编程语言 > Java开发

JAVA线程的概念详解

2015-04-29 21:03 295 查看
 和其他多数计算机语言不同,Java内置支持多线程编程(multithreaded programming)。

多线程程序包含两条或两条以上并发运行的部分。程序中每个这样的部分都叫一个线程(thread),每个线程都有独立的执行路径。因此,多线程是多任务处理的一种特殊形式。

你一定知道多任务处理,因为它实际上被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:基于进程的和基于线程的。认识两者的不同是十分重要的。

对很多读者,基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此,基于进程(process-based) 的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。

在基于线程(thread-based) 的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。例如,一个文本编辑器可以在打印的同时格式化文本。所以,多进程程序处理“大图片”,而多线程程序处理细节问题。

多线程程序比多进程程序需要更少的管理费用。进程是重量级的任务,需要分配它们自己独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制。

多线程帮助你写出CPU最大利用率的高效程序,因为空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。举个例子来说,网络的数据传输速率远低于计算机处理能力,本地文件系统资源的读写速度远低于CPU的处理能力,当然,用户输入也比计算机慢很多。在传统的单线程环境中,你的程序必须等待每一个这样的任务完成以后才能执行下一步——尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。

如果你在Windows 98 或Windows 2000这样的操作系统下有编程经验,那么你已经熟悉了多线程。然而,Java管理线程使多线程处理尤其方便,因为很多细节对你来说是易于处理的。

一、操作系统中线程和进程的概念

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

 

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

 

“同时”执行是人的感觉,在线程之间实际上轮换执行。

 

二、Java中的线程

在Java中,“线程”指两件不同的事情:

1、java.lang.Thread类的一个实例;

2、线程的执行。

 

使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

 

一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。

 

Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。

 

一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

 

一旦创建一个新的线程,就产生一个新的调用栈。

 

线程总体分两类:用户线程和守候线程。

当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。

1、进程和线程的概念

进程:运行中的应用程序称为进程,拥有系统资源(cpu、内存)

线程:进程中的一段代码,一个进程中可以有多段代码。本身不拥有资源(共享所在进程的资源)

在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程。

区别:

 1、是否占有资源问题

 2、创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大。

 3、进程为重量级组件,线程为轻量级组件

多进程: 在操作系统中能同时运行多个任务(程序)

多线程: 在同一应用程序中有多个功能流同时执行

 

2、线程的主要特点

①、不能以一个文件名的方式独立存在在磁盘中;

②、不能单独执行,只有在进程启动后才可启动;

③、线程可以共享进程相同的内存(代码与数据)。

3、线程的主要用途

①、利用它可以完成重复性的工作(如实现动画、声音等的播放)。

②、从事一次性较费时的初始化工作(如网络连接、声音数据文件的加载)。

③、并发执行的运行效果(一个进程多个线程)以实现更复杂的功能

4、多线程(多个线程同时运行)程序的主要优点

①、可以减轻系统性能方面的瓶颈,因为可以并行操作;

②、提高CPU的处理器的效率,在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性;另一方面,在多CPU系统中,可以把不同的线程在不同的CPU中执行,真正做到同时处理多任务。

线程的概述(Introduction)

线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。

注意:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。

 

什么是多线程?
  在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。最开始的时候,那些掌握机器低级语言的程序员编写一些“中断服务例程”,主进程的暂停是通过硬件级的中断实现的。尽管这是一种有用的方法,但编出的程序很难移植,由此造成了另一类的代价高昂问题。中断对那些实时性很强的任务来说是很有必要的。但对于其他许多问题,只要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。
  最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个线程不能将信息同时发送给一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。
  多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。   最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。同时它的出现也带来了一些问题。

使用线程的好处

  ·使用线程可以把占据时间长的程序中的任务放到后台去处理

  ·用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度

   ·程序的运行速度可能加快

   ·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。

   还有其他很多使用多线程的好处,这里就不一一说明了。

 使用线程的一些缺点

  ·如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。

   ·更多的线程需要更多的内存空间。

   ·线程可能会给程序带来更多“bug”,因此要小心使用。

   ·线程的中止需要考虑其对程序运行的影响。

   ·通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。

 
线程的概述(Introduction)
线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。
注意:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。
 
线程的定义(Defining)
定义一个线程(Defining a Thread)有两种方法
1) 继承java.lang.Thread类
1.            /**  

2.             * 使用继承java.lang.Thread类的方式创建一个线程  

3.             *   

4.             * @author DreamSea 2011-12-29 20:17:06  

5.             */ 

6.            public class ThreadTest extends Thread {  

7.             

8.                /**  

9.                 * 重写(Override)run()方法 JVM会自动调用该方法  

10.             */ 

11.            public void run() {  

12.                System.out.println("I'm running!");  

13.            }  

14.        } 

注意:重写(override)run()方法在该线程的start()方法被调用后,JVM会自动调用run方法来执行任务;但是重载(overload)run()方法,该方法和普通的成员方法一样,并不会因调用该线程的start()方法而被JVM自动运行。 例如:
1.            public class ThreadTest extends Thread {  

2.             

3.                /**  

4.                 * 重写(Override)run()方法 JVM会自动调用该方法  

5.                 */ 

6.                @Override 

7.                public void run() {  

8.                    System.out.println("I'm running!");  

9.                }  

10.         

11.            /**  

12.             * 重载(Overload)run()方法 和普通的方法一样,并不会在该线程的start()方法被调用后被JVM自动运行  

13.             */ 

14.            public void run(int times) {  

15.                System.out.println("I'm running!(Overload)");  

16.            }  

17.        } 

不建议使用此方法定义线程,因为采用继承Thread的方式定义线程后,你不能在继承其他的类了,导致程序的可扩展性大大降低。
 
2) 实现java.lang.Runnable接口
1.            /**  

2.             * 通过实现Runnable接口创建一个线程  

3.             * @author DreamSea  

4.             */ 

5.            public class ThreadTest implements Runnable {  

6.                public void run() {  

7.                        System.out.println("I'm running!");  

8.                }  

9.            } 

线程的启动(Starting)
任何一个线程的执行的前提都是必须有Thread class的实例存在,并且通过调用run()方法启动线程。
1)如果线程是继承Thread类,则创建方式如下:
1.            ThreadTest1 tt = new ThreadTest1();  

2.            tt.start(); 

2)如果是实现Runnable接口,则创建方式如下:
1.            ThreadTest2 tt = new ThreadTest2();  

2.            Thread t = new Thread(tt);  

3.            t.start(); 

线程的状态(State)
新生状态(New): 当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,此时该线程处于新生(new)状态,处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的(not alive);
 
就绪状态(Runnable): 通过调用线程实例的start()方法来启动线程使线程进入就绪状态(runnable);处于就绪状态的线程已经具备了运行条件,但还没有被分配到CPU即不一定会被立即执行,此时处于线程就绪队列,等待系统为其分配CPCU,等待状态并不是执行状态;此时线程是活着的(alive);
 
运行状态(Running): 一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程的run()方法才开始被执行;在运行状态的线程执行自己的run()方法中的操作,直到调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的等待状态;此时线程是活着的(alive);
 
阻塞状态(Blocked):通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞(blocked)状态;处于Blocking状态的线程仍然是活着的(alive)
 
死亡状态(Dead):当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡(dead)状态。此时可能仍然存在一个该Thread的实例对象,当该Thready已经不可能在被作为一个可被独立执行的线程对待了,线程的独立的call stack已经被dissolved。一旦某一线程进入Dead状态,他就再也不能进入一个独立线程的生命周期了。对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime
exception)的异常;处于Dead状态的线程不是活着的(not alive)。
 
线程状态图
 


线程的方法(Method)、属性(Property)
1)优先级(priority)
每个类都有自己的优先级,一般property用1-10的整数表示,默认优先级是5,优先级最高是10;优先级高的线程并不一定比优先级低的线程执行的机会高,只是执行的机率高;默认一个线程的优先级和创建他的线程优先级相同;
 
2)Thread.sleep()/sleep(long millis)
当前线程睡眠/millis的时间(millis指定睡眠时间是其最小的不执行时间,因为sleep(millis)休眠到达后,无法保证会被JVM立即调度);sleep()是一个静态方法(static method) ,所以他不会停止其他的线程也处于休眠状态;线程sleep()时不会失去拥有的对象锁。作用:保持对象锁,让出CPU,调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留一定的时间给其他线程执行的机会;
 
3)Thread.yield()
让出CPU的使用权,给其他线程执行机会、让同等优先权的线程运行(但并不保证当前线程会被JVM再次调度、使该线程重新进入Running状态),如果没有同等优先权的线程,那么yield()方法将不会起作用。
 
4)thread.join()
使用该方法的线程会在此之间执行完毕后再往下继续执行。
 
5)object.wait()
当一个线程执行到wait()方法时,他就进入到一个和该对象相关的等待池(Waiting Pool)中,同时失去了对象的机锁—暂时的,wait后还要返还对象锁。当前线程必须拥有当前对象的锁,如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常,所以wait()必须在synchronized
block中调用。
 
6)object.notify()/notifyAll()
唤醒在当前对象等待池中等待的第一个线程/所有线程。notify()/notifyAll()也必须拥有相同对象锁,否则也会抛出IllegalMonitorStateException异常。
 
7)Synchronizing Block
Synchronized Block/方法控制对类成员变量的访问;Java中的每一个对象都有唯一的一个内置的锁,每个Synchronized Block/方法只有持有调用该方法被锁定对象的锁才可以访问,否则所属线程阻塞;机锁具有独占性、一旦被一个Thread持有,其他的Thread就不能再拥有(不能访问其他同步方法),方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: