这里我们做一个完整的样例来说明线程产生的方式不同而生成的线程的差别:
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实例)的多个线程。
下节我们将研究线程对象方法,还是那句话,一般文档中能够读到的内容我不会介绍太多
请大家自己了解。
线程对象的几个重要的方法
虽然线程对象的经常用法能够通过API文档来了解,可是有非常多方法只从API说明是无法具体了解的。
本来打算用一节的篇幅来把线程方法中一些重要的知识说完,但这样下来预计要非经常的篇幅,可能要用好几节才干说把和线程方法相关的一些重要的知识说完。
首先我们接基础篇(二)来说明start()方法。
一个线程对象生成后,假设要产生一个运行的线程,就一定要调用它的start()方法.在介绍这种方法时不得不同一时候说明run方法.事实上线程对 象的run方法全然是一个接口回调方法,它是你这个线程对象要完毕的详细逻辑.简单说你要做什么就你在run中完毕,而怎样做,什么时候做就不须要你控制 了,你仅仅要调用start()方法,JVM就会管理这个线程对象让它产生一个线程并注冊到线程处理系统中。
从表面上看,start()方法调用了run()方法,其实,start()方法并没有直接调用run方法.在JDK1.5曾经 start()方法是本地方法,它怎样终于调用run方法已经不是JAVA程序猿所能了解的.而在JDK1.5中,原来的那个本地start()方法被 start0()取代,另个一个纯JAVA的start()中调用本地方法start0(),而在start()方法中做了一个验证,就是对一个全局变量 (对象变量)started做检验,假设为true,则start()抛出异常,不会调用本地方法start0(),否则,先将该变量设有true,然后 调用start0()。
从中我们能够看到这个为了控制一个线程对象仅仅能执行成功一次start()方法.这是由于线程的执行要获取当前环境,包含安全,父线程的权限, 优先级等条件,假设一个线程对象能够执行多次,那么定义一个static 的线程在一个环境中获取对应权限和优先级,执行完毕后它在还有一个环境中利用原来的权限和优先级等属性在当前环境中执行,这样就造成无法预知的结果.简单说 来,让一个线程对象仅仅能成功执行一次,是基于对线程管理的须要。
start()方法最本质的功能是从CPU中申请还有一个线程空间来执行 run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间执行,也就是说,假设你直接调用线程对象的run()方法,当然也会执行,但那是 在当前线程中执行,run()方法执行完毕后继续执行以下的代码.而调用start()方法后,run()方法的代码会和当前线程并发(单CPU)或并行 (多CPU)执行。
所以请记住一句话[调用线程对象的run方法不会产生一个新的线程],尽管能够达到同样的运行结果,但运行过程和运行效率不同。
[线程的interrupt()方法,interrupted()和isInterrupted()]
这三个方法是关系非常密切并且又比較复杂的,尽管它们各自的功能非常清楚,但它们之间的关系有大多数人不是真正的了解。
先说interrupt()方法,它是实例方法,而它也是最奇怪的方法,在java语言中,线程最初被设计为"隐晦难懂"的东西,直到如今它的 语义不没有象它的名字那样准确。大多数人以为,一个线程象调用了interrupt()方法,那它相应的线程就应该被中断而抛出异常,事实中,当一个线程 对象调用interrupt()方法,它相应的线程并没有被中断,仅仅是改变了它的中断状态。
使当前线程的状态变以中断状态,假设没有其他影响,线程还会自己继续运行。
仅仅有当线程运行到sleep,wait,join等方法时,或者自己检查中断状态而抛出异常的情况下,线程才会抛出异常。
假设线程对象调用interrupt()后它相应的线程就马上中断,那么interrupted()方法就不可能运行。
由于interrupted()方法是一个static方法,就是说仅仅能在当前线程上调用,而假设一个线程interrupt()后它已经中断了,那它又怎样让自己interrupted()?
正由于一个线程调用interrupt()后不过改变了中断状态,它能够继续运行下去,在没有调用sleep,wait,join等法或自己抛 出异常之前,它就能够调用interrupted()来清除中断状态(还会原状)interrupted()方法会检查当前线程的中断状态,假设为 "被中断状态"则改变当前线程为"非中断状态"并返回true,假设为"非中断状态"则返回false,它不仅检查当前线程是否为中断状态,并且在保证当 前线程回来非中断状态,所以它叫"interrupted",是说中断的状态已经结束(到非中断状态了)isInterrupted()方法则只检查线 程对象相应的线程是否是中断状态,并不改变它的状态。
眼下大家仅仅能先记住这三个方法的功能,仅仅有真正深入到多线程编程实践中,才会体会到它们为什么是对象方法,为什么是类方法。
线程究竟什么时候才被中断抛出InterruptedException异常,我们将在提高篇中具体讨论。
[sleep(),join(),yield()方法]
在如今的环节中,我仅仅能先说明这些方法的作用和调用原则,至于为什么,在基础篇中无法深入,仅仅能在提高篇中具体说明。
sleep()方法中是类方法,也就是对当前线程而言的,程序猿不能指定某个线程去sleep,仅仅能是当前线程执行到sleep()方法时,睡 眠指定的时间(让其他线程执行).其实也仅仅能是类方法,在当前线程上调用.试想假设你调用一个线程对象的sleep()方法,那么这个对象相应的线程如 果不是正在执行,它怎样sleep()?所以仅仅有当前线程,由于它正在执行,你才干保证它能够调用sleep()方法。
原则:[在同步方法中尽量不要调用线程的sleep()方法],或者简单说,对于一般水平的程序猿你基本不应该调用sleep()方法。
join()方法,正如第一节所言,在一个线程对象上调用join方法,是当前线程等待这个线程对象相应的线程结束,比方有两个工作,工作A要耗时10秒钟,工作B要耗时10秒或很多其它。我们在程序中先生成一个线程去做工作B,然后做工作A。
new?B().start();//做工作B
A();//做工作A
工作A完毕后,以下要等待工作B的结果来进行处理.假设工作B还没有完毕我就不能进行以下的工作C,所以
B?b?=?new?B();
b.start();//做工作B
A();//做工作A
b.join();//等工作B完毕。
C();//继续工作C。
原则:[join是測试其他工作状态的唯一正确方法],我见过非常多人,甚至有的是博士生,在处理一项工作时假设还有一项工作没有完毕,说让当前工 作线程sleep(x),我问他,你这个x是怎样指定的,你怎么知道是100毫秒而不是99毫秒或是101毫秒?事实上这就是OnXXX事件的实质,我们不 是要等多长时间才去做什么事,而是当等待的工作正好完毕的时候去做。
yield()方法也是类方法,仅仅在当前线程上调用,理由同上,它主是让当前线程放弃本次分配到的时间片原则:[不是很必要的情况下,没有理 由调用它].调用这种方法不会提高不论什么效率,仅仅是减少了CPU的总周期上面介绍的线程一些方法,基于(基础篇)而言仅仅能简单提及.以后具体应用中我会结合 实例具体论述。
线程本身的其他方法请參看API文档.下一节介绍非线程的方法,但和线程密切相关的两[三]个对象方法:
[wait(),notify()/notifyAll()]
这是在多线程中很重要的方法。
关于这两个方法,有非常多的内容须要说明.在以下的说明中可能会有非常多地方不能一下子明确,但在看完本节后,即使不能全然明确,你也一定要回过头来记住以下的两句话:
[wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法]
[wait(),notify()/notityAll()方法仅仅能在同步方法中调用]
[线程的相互排斥控制]
多个线程同一时候操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响还有一线程对该对象的真正结果.
这个样例我们在太多的文档中能够看到,就象两个操售票员同一时候售出同一张票一样.
线程A | 线程B |
---|---|
1.线程A在数据库中查询存票,发现票C能够卖出 | |
class="left"2.线程A接受用户订票请求,准备出票. | |
3.这时切换到了线程B运行 | |
4.线程B在数据库中查询存票,发现票C能够卖出 | |
5.线程B将票卖了出去 | |
6.切换到线程A运行,线程A卖了一张已经卖出的票 |
所以须要一种机制来管理这类问题的发生,当某个线程正在运行一个不可切割的部分时,其他线程不能不能同一时候运行这一部分.
象这样的控制某一时刻仅仅能有一个线程运行某个运行单元的机制就叫相互排斥控制或共享相互排斥(mutual exclusion)
在JAVA中,用synchornizedkeyword来实现相互排斥控制(临时这样觉得,JDK1.5已经发展了新的机制)
[synchornizedkeyword]
把一个单元声明为synchornized,就能够让在同一时间仅仅有一个线程操作该方法.
有人说synchornized就是一把锁,其实它确实存在锁,可是是谁的锁,锁谁,这是一个很复杂的问题.
每一个对象仅仅有一把监视锁(monitor lock),一次仅仅能被一个线程获取.当一个线程获取了这一个锁后,其他线程就仅仅能等待这个线程释放锁才干再获取.
那么synchornizedkeyword究竟锁什么?得到了谁的锁?
对于同步块,synchornized获取的是參数中的对象锁:
synchornized(obj){
//...............
}
线程运行到这里时,首先要获取obj这个实例的锁,假设没有获取到线程仅仅能等待.假设多个线程运行到这里,仅仅能有一个线程获取obj的锁,然后运行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.
假如:
public void test(){
Object o = new Object(); synchornized(obj){
//...............
}
}
这段程序控制不了不论什么,多个线程之间运行到Object o = new Object();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地运行.
而假设是类的属性:
class Test{
Object o = new Object();
public void test(){ synchornized(o){
//...............
}
}
}
全部运行到Test实例的synchornized(o)的线程,仅仅有一个线程能够获取到监视锁.
有时我们会这样:
public void test(){ synchornized(this){
//...............
}
}
那么全部运行Test实例的线程仅仅能有一个线程运行.而synchornized(o)和synchornized(this)的范围是不同 的,由于运行到Test实例的synchornized(o)的线程等待时,其他线程能够运行Test实例的synchornized(o1)部分,但多 个线程同一时候仅仅有一个能够运行Test实例的synchornized(this).]
而对于
synchornized(Test.class){
//...............
}
这种同步块而言,全部调用Test多个实例的线程赐教仅仅能有一个线程能够运行.
[synchornized方法]
假设一个方法声明为synchornized的,则等同于把在为个方法上调用synchornized(this).
假设一个静态方法被声明为synchornized,则等同于把在为个方法上调用synchornized(类.class).
如今进入wait方法和notify/notifyAll方法.这两个(或叫三个)方法都是Object对象的方法,而不是线程对象的方法.如同锁一样,它们是在线程中调用某一对象上运行的.
class Test{
public synchornized void test(){
//获取条件,int x 要求大于100; if(x < 100)
wait();
}
}
这里为了说明方法没有加在try{}catch(){}中,假设没有明白在哪个对象上调用wait()方法,则为this.wait();
假如:
Test t = new Test();
如今有两个线程都运行到t.test();方法.当中线程A获取了t的对象锁,进入test()方法内.
这时x小于100,所以线程A进入等待.
当一个线程调用了wait方法后,这个线程就进入了这个对象的歇息室(waitset),这是一个虚拟的对象,但JVM中一定存在这种一个数据结构用来记录当前对象中有哪些程线程在等待.
当一个线程进入等待时,它就会释放锁,让其他线程来获取这个锁.
所以线程B有机会获得了线程A释放的锁,进入test()方法,假设这时x还是小于100,线程B也进入了t的歇息室.
这两个线程仅仅能等待其他线程调用notity[All]来唤醒.
可是假设调用的是有參数的wait(time)方法,则线程A,B都会在歇息室中等待这个时间后自己主动唤醒.
[为什么真正的应用都是用while(条件)而不用if(条件)]
在实际的编程中我们看到大量的样例都是用?
while(x < 100)
wait();go();而不是用if,为什么呢?
在多个线程同一时候运行时,if(x <100)是不安全的.由于假设线程A和线程B都在t的歇息室中等待,这时还有一个线程使x==100了,并调用notifyAll方法,线程A继续 运行以下的go().而它运行完毕后,x有可能又小于100,比方以下的程序中调用了--x,这时切换到线程B,线程B没有继续推断,直接运行go(); 就产生一个错误的条件,仅仅有while才干保证线程B又继续检查一次.
[notify/notifyAll方法]
这两个方法都是把某个对象上歇息区内的线程唤醒,notify仅仅能唤醒一个,但到底是哪一个不能确定,而notifyAll则唤醒这个对象上的歇息室中全部的线程.
一般有为了安全性,我们在绝对多数时候应该使用notifiAll(),除非你明白知道仅仅唤醒当中的一个线程.
那么是否是仅仅要调用一个对象的wait()方法,当前线程就进入了这个对象的歇息室呢?事实中,要调用一个对象的wait()方法,仅仅有当前线程获取了这个对象的锁,换句话说一定要在这个对象的同步方法或以这个对象为參数的同步块中.
class MyThread extends Thread{
Test t = new Test();
public void run(){
t.test();
System.out.println("Thread say:Hello,World!");
}
} public class Test { int x = 0;
public void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
这个线程就不会进入t的wait方法而直接打印出Thread say:Hello,World!.
而假设改成:
public class Test { int x = 0;
public synchornized void test(){
if(x==0)
try{
wait();
}catch(Exception e){}
}
public static void main(String[] args) throws Exception{
new MyThread().start();
}
}
我们就能够看到线程一直等待,注意这个线程进入等待后没有其他线程唤醒,除非强行退出JVM环境,否则它一直等待.
所以请记住:
[线程要想调用一个对象的wait()方法就要先获得该对象的监视锁,而一旦调用wait()后又马上释放该锁]