java线程基本知识小结看了一阵子java线程方面的知识,《thinkinginjava3rd》,《effectivejava2nd》,感觉还是雾里看花,难以得其精髓。多线程编程本来就是一门很玄奥的学问,不是看一些基础的语法知识就能真正掌握的。在实践中去揣摩,我想才是最好的方法。奈何我现在没有这样的条件,离论文开题的时间不远了,我还没有摸到头绪,真不知道是该坚持还是放弃。扯远了,还是回到线程来吧,虽然不得要领,但还是要把一些基础的东西总结一下,一旦以后需要用到的时候,也可以方便地回顾。1.线程的创建java中创建一个线程有两种方式:1.1.扩展Thread类,并重载run()方法publicclassThreadNameextendsThread{publicvoidrun(){//dosomethinghere}}1.2.实现runnable接口,并调用Thread提供的构造函数publicclassThreadNameimplementsRunnable{publicvoidrun(){//TODOAuto-generatedmethodstub}publicvoidmain(Stringargs[]){Threadthread=newThread(newThreadName());thread.start();}}这两种方法各有利弊。简单来说,如果你确定当前的类就是作为一个单纯的线程来实现,不需要再继承其他任何类的时候,那么最好就用第一种方式,因为简单,而且可以直接就获得Thread所提供的各种方法,在类内部使用;反之,如果你需要继承其他类,或者将来可能会有这种需要,那么就用第二种方法。第二种方法需要注意的地方是,当你实现了Runnable接口后,你仅仅是实现了该接口而已,你现在所有的只是一个run()方法,即使你生成一个对象来调用run()方法,和普通的方法调用也没什么两样,并不会创建一个新的线程。只有当你用Thread构造函数创建一个对象之后,这才是新创建了一个线程。当你使用第二种方法的时候,可能你也想在类内部调用Thread所提供的一些方法,这时可以用Thread.currentThread()来获得当前线程的引用。2.线程的运行当你获得一个线程实例thread后,使他开始运行的唯一方法就是thread.start(),由java虚拟机调用该线程的run方法。注意不是调用run()方法来启动线程。当你调用run()方法时,如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。另外要注意,多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。3.结束线程运行就我目前所知,有两种结束线程的方法(我是指通过直接操作线程对象的合法方法)。3.1.在线程内部设置一个标记为volatile的标志位publicclassStopThreadextendsThread{publicvolatilebooleanstop=false;privatestaticinti=0;publicvoidrun(){while(!stop){i++;}System.out.println(i);}/***@paramargs*/publicstaticvoidmain(String[]args){StopThreadthread=newStopThread();thread.start();try{sleep(2000);}catch(InterruptedExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}thread.stop=true;}}关于volatile的说明可能需要先了解JAVA的内存模型,简单点说就是JVM有个主存,各个线程再有各自的工作内存,两个存放的地方带来的问题就是不一致。volatile就是为了解决这个不一致出现的。使用volatile会每次修改后及时的将工作内存的内容同步回主存。这样,线程所看到的就是最新的值,而不是被缓存的值。注,这里还牵涉到“原子操作”的概念。所谓原子操作,大体上就是指操作只由一个指令即可完成,不需要上下文切换。在java中,对除long和double之外的基本类型进行简单的赋值或者返回值操作的时候,才是原子操作。然而,只要给long或double加上volatile,就和其他基本类型一样了。但自增操作并不是原子操作,它牵涉到一次读一次写!正因为对boolean的操作是原子操作,我们不用担心多个线程同时对boolean值进行修改而导致不一致的情况,所以在修改、读取boolean值的时候不需要加synchronized关键字。3.2.调用interrrupt()方法有的时候,线程可能会阻塞,比如在等待输入的时候,并且他也不能轮询结束标志。这个时候,可以用Thread.interrupt()方法来跳出阻塞代码。publicclassBlockedextendsThread{publicBlocked(){System.out.println(Starting);}publicvoidrun(){try{synchronized(this){wait();}}catch(InterruptedExceptione){System.out.println(Interrupted);}System.out.println(Exitingrun());}publicstaticvoidmain(Stringargs[]){Blockedthread=newBlocked();thread.start();try{sleep(2000);}catch(InterruptedExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}thread.interrupt();thread=null;}}注意,我们在用interrupt终止线程后,最好再将该线程赋为null,这样垃圾回收器就可以回收该线程了。另,使用interrupt()并不需要获得对象锁-这与wait()、notify()等不同上面例子中有一个比较tricky的地方:当我们使用interrupt()方法中断线程的运行时,线程将抛出InterruptedException,但在抛出exception的同时,他的中断状态将被清除,所以如果我们在catch(InterrruptedExceptione){}里调用isInterrupted(),返回的结果将会是false。当你使用Timer类调度线程的时候,可以使用Timer类提供的cancel()方法来终止线程的运行。Timer类还是比较好用的,具体参见APIdoc。4.wait(),notify(),notifyAll()这三个方法是线程同步机制的基础,但这三种方法已经被JoshuaBloch视为“low-level”的,“汇编级别”的代码,应该尽量被JDK1.5以来提供的高层次框架类取代。这正是java让人又爱又恨的地方-它总是提供各种方便易用的API供使用者调用,帮助编程人员提高效率,避免错误,但与此同时,它也在无形之间将底层机制与使用隔离,使相当一批编程者“沦为”API的“纯”调用者,只懂得用一堆API来堆起一个程序。很不幸,我就是其中之一。但我总算还保留着一点求知的欲望。使用wait(),总是要最先想到,一定要用while循环来判断执行条件是否满足:synchronized(obj){while(conditionIsNotMet){wait();}//Performactionapproriatetocondition}这样就可以保证在跳出等待循环之前条件将被满足,如果你被不相干的条件所通知(比如notifyAll()),或者在你完全退出循环之前条件已经改变,你被确保可以回来继续等待。对于notify(),notifyAll(),JoshuaBloch的建议是尽量使用notifyAll(),以避免出现某些进程永远沉睡的现象。5.synchronizedsynchronized是将对象中所有加锁的方法(or代码块)锁定。由于同一时间只能有一个线程拥有对象的锁,这也就保证了互斥。有两种加锁形式:5.1.给代码块加锁:synchronized(obj){//somecodeshere}5.2.给方法加锁:publicsynchronizedvoidmethod(){//somecodeshere}注意,java中不允许在重载(重写?)的时候改变签名,但sychronized关键字并不属于签名,因此,你可以继承一个类,然后重载(重写?)这个几类的方法,加上sychronized关键字来保证互斥以上介绍了java线程中语法上的一些基础的东西,下面要介绍的同样也是基础,但同上面而言还是有些差异,还是分开一段来介绍的好。1.一些方法sleep():sleep()方法能迫使线程休眠指定长的时间。在调用sleep()方法的时候,必须把它放在try块中,因为在休眠时间到期之前有可能被打断。如果某人持有对此线程的引用,并且在此线程上调用了interrupt()方法,就会发生这种情况。daemon线程:必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。一个后台线程所创建的任何线程都将被自动设置成后台线程join():一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为false)你也可以在调用join()时带上一个超时参数(单位可以是毫秒或者毫秒+纳秒),这样如果目标线程在这段时间到期还没结束的话,join()方法总能返回。对join()方法的调用可以被中断,做法是在调用线程上使用interrupt()方法,这时需要用到try-catch2.线程的四种状态:创建、就绪、死亡、阻塞。应该还有一个运行状态吧线程进入阻塞状态可能有如下四种原因:2.1.通过调用sleep()使线程进入休眠状态。在这种情况下,线程在指定时间内不会运行2.2.通过调用wait()使线程挂起,直到线程得到了notify()或notifyAll()消息,线程才会进入就绪状态2.3.线程在等待输入/输出操作的完成2.4.线程试图在某个对象上调用其同步控制方法,但是对象锁不可用3.只有当下列四个条件同时满足时,才会发生死锁:3.1.互斥条件:线程使用的资源中至少又一个是不能共享的3.2.至少有一个进程持有一个资源,并且他在等待获取一个当前被别的进程持有的资源。3.3.资源不能被进程抢占。所有的进程必须把资源释放作为普通事件。3.4.必须有循环等待,即,一个线程等待其他线程持有的资源,后者又在等待另一个进程持有的资源,这样一直下去,直到又一个进程在等待第一个进程持有的资源,使得大家都被锁住。要发生死锁,必须这四个条件同时满足,所以,只要破坏其中任意一个,就可以打破死锁。其中第四个条件是最容易被打破的。4.java中对以“管道”形式对线程的输入/输出提供了支持。PipedWriter类允许线程向管道写;PepedReader类允许不同线程从一个管道中读取。下面是对《effectivejava2nd》中Concurrency一章的总结。感觉这一章并不如我所想象的那样,对java的线程机制有一个全面透彻的解说,反而是花了很大力气宣传一本书-《JavaConcurrencyinPractice》。好吧,想在一章的内容里对java线程的认识达到某种高度,怎么想也是不太现实的。但这本书究竟如何,我还没看过,不作评论,但我想肯定是很适合正在用java线程做项目的人的。对我而言,重要的不是学会怎么用java代码来写出多线程程序,而是搞清线