一、线程的概念:
线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈。所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程被称为轻负荷进程(light-weight process)。一个进程中可以包含多个线程。 一个线程是一个程序内部的顺序控制流。 1. 进程:每个进程都有独立的代码和数据空间(进程上下文) ,进程切换的开销大。 2. 线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(pc),线程切换的开销小。 3. 多进程:在操作系统中,能同时运行多个任务程序。 4. 多线程:在同一应用程序中,有多个顺序流同时执行。 java内在支持多线程,它的所有类都是在多线程下定义的,java利用多线程使整个系统成为异步系统。 1. 虚拟的cpu,封装在java.lang.thread类中。 2. cpu所执行的代码,传递给thread类。 3. cpu所处理的数据,传递给thread类。 二、线程的构造 线程实例表示java解释器中的真正的线程,通过它可以启动线程、终止线程、线程挂起等,每个线程都是通过类thread在java的软件包java.lang中定义,它的构造方法为: public thread (threadgroup group,runnable target,string name); 其中,group 指明该线程所属的线程组;target实际执行线程体的目标对象,它必须实现接口runnable; name为线程名。java中的每个线程都有自己的名称,java提供了不同thread类构造器,允许给线程指定名称。如果name为null时,则java自动提供唯一的名称。 当上述构造方法的某个参数为null时,我们可得到下面的几个构造方法: public thread (); public thread (runnable target); public thread (runnable target,string name); public thread (string name); public thread (threadgroup group,runnable target); public thread (threadgroup group,string name); 一个类声明实现runnable接口就可以充当线程体,在接口runnable中只定义了一个方法 run(): public void run(); 任何实现接口runnable的对象都可以作为一个线程的目标对象,类thread本身也实现了接口runnable,因此我们可以通过两种方法实现线程体。 (一)定义一个线程类,它继承线程类thread并重写其中的方法 run(),这时在初始化这个类的实例时,目标target可为null,表示由这个实例对来执行线程体。由于java只支持单重继承,用这种方法定义的类不能再继承其它父类。 (二)提供一个实现接口runnable的类作为一个线程的目标对象,在初始化一个thread类或者thread子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体 run()。这时,实现接口runnable的类仍然可以继承其它父类。 三、线程的状态 每个线程都是通过某个特定thread对象的方法run( )来完成其操作的,方法run( )称为线程体。下图表示了java线程的不同状态以及状态之间转换所调用的方法。 1. 创建状态(new thread) 执行下列语句时,线程就处于创建状态: thread mythread = new thread( ); 当一个线程处于创建状态时,它仅仅是一个空的线程对象,系统不为它分配资源。 2. 可运行状态( runnable ) thread mythread = new thread( ); mythread.start( ); 当一个线程处于可运行状态时,系统为这个线程分配了它需的系统资源,安排其运行并调用线程运行方法,这样就使得该线程处于可运行( runnable )状态。需要注意的是这一状态并不是运行中状态(running ),因为线程也许实际上并未真正运行。由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,java的运行系统必须实现调度来保证这些线程共享处理器。 3. 不可运行状态(not runnable) 进入不可运行状态的原因有如下几条: 1) 调用了sleep()方法; 2) 调用了suspend()方法; 3) 为等候一个条件变量,线程调用wait()方法; 4) 输入输出流中发生线程阻塞; 不可运行状态也称为阻塞状态(blocked)。因为某种原因(输入/输出、等待消息或其它阻塞情况),系统不能执行线程的状态。这时即使处理器空闲,也不能执行该线程。 4. 死亡状态(dead) 线程的终止一般可通过两种方法实现:自然撤消(线程执行完)或是被停止(调用stop()方法)。目前不推荐通过调用stop()来终止线程的执行,而是让线程执行完。 四、有关线程的一些长用的方法 1. sleep(long millis) 这个方法是一个静态的方法,也就是说我们可以直接调用它,如thread.sleep(5000)就是指让目前正在运行的线程先停下工作等待5000毫秒。有一点需要注意的是:不能肯定这个线程在过5000毫秒肯定会立刻被执行。 2.interrupt() 这个方法用来打断一个线程(感觉说睡眠中的进程更加合适)。这个方法的作用可以举个例子来看一下: public class testinterrupt extends thread { /** creates a new instance of testinterrupt */ public testinterrupt() { } public void run() { try { for ( int i=0; i<5; i++) { system.out.println("running the first loop " + i); } thread.sleep(10000); for ( int i=6; i<10; i++) { system.out.println("running the second loop" + i); } }catch (interruptedexception ie) { system.out.println("sleep interrupted in run()"); for ( int i=11; i<15; i++) { system.out.println("running the third loop" + i); } } } public static void main(string[] args) { testinterrupt ti = new testinterrupt(); thread t = new thread(ti); t.start(); //delay for a few seconds to let the other thread get going try { thread.sleep(2500); }catch (interruptedexception ie) { system.out.println("sleep interrupted in main()"); } system.out.println("about to wake up the other thread"); t.interrupt(); system.out.println("exiting from main"); } } 上面的例子中假如没有t.interropt()的话,程序运行做的就是 for ( int i=6; i<10; i++) { system.out.println("running the second loop" + i); } 加上以后做的就是catch中的内容了. 3.join()和join(long millis) join()这个函数的作用是使得目前正在运行的线程假如为a停下来,一直到调用join()方法的这个线程b被执行完毕,再继续一开始的线程a; 看个例子好了: public class testjoin1 extends thread { /** creates a new instance of testjoin1 */ public testjoin1() { } public void run() { try { for ( int i=0; i<5; i++) { system.out.println("running the first loop " + i); } thread.sleep(1000); for ( int i=6; i<10; i++) { system.out.println("running the second loop" + i); } }catch (interruptedexception ie) { system.out.println("sleep interrupted in run()"); } } public static void main(string[] args) { try { testjoin1 ti = new testjoin1(); thread t = new thread(ti); t.start(); t.join(); for ( int i=11; i<15; i++) { system.out.println("running the third loop" + i); } }catch (interruptedexception ie) { system.out.println("join interrupted in run()"); } system.out.println("exiting from main"); } } 这个程序的结果是先让t.join()时刻正在运行的线程(其实就是main)被搁置,做完了t这个线程的所有内容,再回到main线程继续做的t.join();语句后剩下的内容.假如把t.join()这行去掉的话,在一般的计算机上跑出来的结果应该是先做了main所有的内容再去做t线程的内容. join(long millis)这个方法和join()方法差不多,都是正在执行的线程a被搁置,去做调用join(long millis)这个方法的线程b的run里面的内容。但后面的millis这个参数决定了b这个线程能被优先运行多少时间(millis代表多少毫秒),millis豪秒过后b线程即使没有运行完毕,也会回到线程a. 底下的一个程序能很好的说明这个问题: public class testjoin2 extends thread { /** creates a new instance of testjoin2 */ public testjoin2() { } public void run() { try { for ( int i=0; i<5; i++) { system.out.println("running the first loop " + i); } thread.sleep(3500); for ( int i=6; i<10; i++) { system.out.println("running the second loop" + i); } }catch (interruptedexception ie) { system.out.println("sleep interrupted in run()"); } } public static void main(string[] args) { try { testjoin2 t2 = new testjoin2(); thread t = new thread(t2); t.start(); t.join(3000); for ( int i=11; i<15; i++) { system.out.println("running the third loop" + i); } }catch (interruptedexception ie) { system.out.println("join interrupted in run()"); } system.out.println("exiting from main"); } } 看了这么多以后,好象很容易产生一种误解join()这个函数就是让调用这个方法的线程b优先(第一个)被执行.其实事实并不是这样的,join()的作用如上面所说的,它只能让目前运行的线程a搁置等,等b执行完毕再开始执行a. 底下的程序可以让人消除这中误解: public class test extends thread { public test(string a) { super(a); } public void run() { system.out.println(this.getname()); } public static void main(string [] args) { test a = new test("a"); test b = new test("b"); test c = new test("c"); a.start(); b.start(); c.start(); try { c.join(); } catch(exception e){ } system.out.println("this is main!"); } } 看了运行结果是a,b先被执行了,然后才是c,最后是main^^; 4.关于synchronized 这个关键字出现的目的是为了让几个线程能同步,举一个最简单的例子。一个电影院有20张票要卖,它有3个售票员。 写个程序来证实一下不用synchronized的结果好了,需要使用sleep()函数来制造出这种可能(线程的执行时机谁也不能预料)出现的情况: public class sell { public static void main(string [] args) { sellthread sell = new sellthread(); thread sell1 = new thread(sell,"sellman1"); thread sell2 = new thread(sell,"sellman2"); thread sell3 = new thread(sell,"sellman3"); sell1.start(); sell2.start(); sell3.start(); } } class sellthread implements runnable { private int i = 20; public void run() { while(true) { if( i > 0) { try { thread.sleep(100); } catch(exception e) { } system.out.println(thread.currentthread().getname() + " sell " + i--); } } } } 结果一共卖掉了22张票(估计电影院以为无缘无故多收了门票钱会很高兴,不过一会也许就要面对愤怒的顾客了....) 这个时候我们的synchronized应该发挥作用了^^修改程序如下: public class sell2 { public static void main(string [] args) { sellthread sell = new sellthread(); thread sell1 = new thread(sell,"sellman1"); thread sell2 = new thread(sell,"sellman2"); thread sell3 = new thread(sell,"sellman3"); sell1.start(); sell2.start(); sell3.start(); } } class sellthread implements runnable { private int i = 20; string a = "now ok!"; public void run() { while(true) { synchronized(a) { if( i > 0) { try { thread.sleep(100); } catch(exception e) { } system.out.println(thread.currentthread().getname() + " sell " + i--); } } } } } 这样就好了只会卖20张票了, synchronized()中的括号中需要的是一个class的对象所以我们不能直接在括号中写上i,就定义了一个string的对象a,a的标志旗(不知道说什么更合适)本来为1代表大家都能使用,这样一个售票员selln的卖票线程拿到了a以后他就可以开始卖票,同时他把a这个对象标志旗置为0,然后其他售票员卖票的线程发现他们拿不到a这个对象了就只先搁置了.一直到selln的卖票线程释放了a,a的标志旗就又变成了1,这个时候其他售票员的卖票的线程就可以竞争了,看谁先拿到a这个对象.不过string a和卖票没什么关系,所以我们可以用this来代替synchronized()中的a,它和a的效果一样表示谁拿到了this对象才能执行. 这里有两个容易误解的地方: (1).一个线程拿到synchronized的括号中的对象之后,其他也要需要拿到这个对象才能运行的线程不能被执行了.其实是其他线程也是可以执行的,但他们执行到了需要synchronized中对象的时候,他们发现对象的标志旗为0,所以只能又被搁置了。(看来幸运女神只能同时光顾一个人^^)所以我们用synchronized来使得线程同步的时候是以牺牲效率为代价的,所以不需要使用的地方就别用好了. (2),一个线程拿到synchronized的括号中的对象之后,其他任何线程都不能执行了,其实假如其他不需要synchronized的对象才能继续执行的线程还是可以和拿到synchronized的括号中的对象的线程一起运行的。 有的方法前面被加上了synchronized.其实这个时候就是把这个方法的调用者,也就是this的标志旗置0了,他不能和其他需要this才能运行的线程一起执行,但可以和其他不需要这个this对象的线程一起运行。 5.wait()和notify()或者notifyall() 这个几个函数是为了使得几个同步的线程按照一定的先后顺序执行。 都是和synchronized()一起使用,设()中对象为obj吧。 有一点需要注意的是,我们应该让需要obj.wait的线程先启动。因为执行顺序是需要obj.wait()的线程a先启动,然后它运行到obj.wait()的时候,进入搁置状态,让其他线程先执行。于是带用obj.notify()的线程b开始执行了,一直到b执行到了obj.notify()以后(obj.notify()实际上就是通知因为obj.wait()被搁置的线程:"轮到你了"),b被搁置,然后继续做a的obj.wait()以后的内容. 所以我们假如让带有obj.notify()的线程b先运行的话,那么b执行完毕以后,b执行的obj.notify()没有找到任何在因obj.wait()而进入搁置状态的线程.然后开始做带有obj.wait()的线程a的话.a运行到了obj.wait()就进入搁置状态,等待另外一个线程中的obj.notify()来唤醒它,不过可惜它永远也等不到了,因为带有obj.notify()的线程已经到搁置的线程中来找过它一次,很可惜的是没找到.于是线程a就一直搁置了...(感觉有点像爱情剧...); 举个例子好了: public class threadtest { public static void main(string [] args) { storage stor = new storage(); counter a = new counter(stor); printer b = new printer(stor); a.start(); b.start(); } } class storage { public int i = 0; } class counter extends thread { private storage a; public counter(storage stor) { a = stor; } public void run() { system.out.println("hi"); try { sleep(100); } catch(exception e) {} int i = 0; while(i < 5) { synchronized(a) { system.out.println("counter"); a.i = (int)(math.random()*50); system.out.println(a.i); a.notify(); } system.out.println("counter2"); ++i; } } } class printer extends thread { private storage a; public printer(storage stor) { a = stor; } public void run() { int i = 0; while(i < 5) { synchronized(a) { system.out.println("printer"); try{ a.wait(); } catch(interruptedexception e) {} system.out.println(a.i); } system.out.println("printer2"); ++i; } } } 运行好了以后把 try { sleep(100); } catch(exception e) {} 这几行注释掉再看看运行结果吧。 还有两点说下: (1).假如几个线程因为obj.wait()进入搁置的话,那么只要一个obj.notifyall()执行以后,他们都处于可以运行状态,不过到底谁先运行我们不就知道。 (2).sleep()和wait()有时候可以执行相同的功能不过要注意的是thread.sleep(long a)过了a毫秒以后,表示可以开始执行了,不代表thread立刻被执行。thread.wait()一但接受到了thread.notify()以后是立刻被执行的. |
|