JAVA有两种线程API,一种是文明的,一种是野蛮的。
野蛮的指诸如stop, suspend, resume这样的东西。JAVA赞成用文明的函数,软性的API。如sleep, wait这些。原因没有研究过也没有结果。
静态方法:
sleep(), 指使得当前线程进入睡眠状态。但其实如果使用0为参数的话,它根本就不会睡。但是这个语义要保持,就是说睡眠这个语义不能少了0这个参数是吧,不能睡负数但是0还是可以的。这样子函数就不难理解是吧。这个函数可以被中断。
yield(), 使得当前线程进入近似睡眠的状态,其实的确是可以为它指定休眠时间的。只是如果在这之前系统有调度空闲的话,这个线程会重新获得执行的权力。它只是一种忙闲配合的逻辑。YIELD还有一个问题就是它为什么不抛出INTERRUPTEDEXCEPTION,原因是它的语义本来就没有离开就绪的状态。它只是被剥夺了运行状态而已,它的语义比SLEEP还要轻。也就是说SLEEP有两个语义:第一放弃运行态,第二进入睡眠。但是它只有一个即放弃运行态,所以不需要被“唤醒”,因为它本来就是“醒”的。就绪状态的线程不需要任何操作就能被调度。而调度是虚拟机的事,跟应用无关,系统不提供直接的调度入口。我们可以停止线程,但不能真正去调度一个线程。原因也不知道。也没有研究。
成员方法:
join(), 阻塞当前调用线程并且等待被调用线程死亡。Windows系统是这么说的,JAVA是这么说的:wait for this thread to die。由此也可见,JAVA总是能提供更自然及更高级别的语义。意思是,调用这个方法的时候,涉及到两个线程,被调用的线程,调用的线程。调用的线程被阻塞,去等待被调用线程死亡。当被调用线程真正死亡的时候,阻塞就完成或者说失效。它可以加两个参数表示超时的毫秒,纳秒,但是不影响它本来的语义:wait for "this" thread to die. 这句话省略了主语,但是要注意它其实是有主语的,主语即当前线程。也即,它是一个能在两个线程间建立关系的API。什么关系?执行关系:比如B.join()的话, 那么如果B不结束,那么当前调用它的线程或者说执行这句话的线程,call it A,那么它也就不会往下执行,就停在这里。直到B结束。它与同步差不多,只不过宾语不一样:同步是我等你把锁交出来,JOIN则是我等你死。
interrupt(), 这个语义是中断被调用的线程。因为它是线程类的方法。但实际上它并没有真正去做这个,只是做了一个内存标志位,只是一个内存操作,跟处理器调度没有半点关系。官方说支持用这种方法停止一个线程而不是使用野蛮式的stop。但是interrupt挺麻烦的就是它的语义还涉及到阻塞状态中的线程的唤醒。如果阻塞线程比如wait, sleep, join(join也可以导致线程阻塞,阻塞在其它线程的生命上--只要人家还是活的,它就做不下去,就是你死我活你活我死的逻辑)被使用interrupt唤醒的话,interrupted标志位将会被清零。这样如果想使用它去停止线程的话,就需要重新将它置位。所以它基本上没有用就是这个原因。再加上我要做个标志位是不是很简单啊是吧,所以说它是真的没有用。
wait()与notify(), 这个非常恶心。用的时候一定要非常小心(其实关于线程的API哪个不是呢),因为一不小心程序就可能假死。根本没有死锁,但是程序得不到执行。先说它的语义:wait是Object而不是Thread的方法,它的使用将把当前线程挂到被调用对象的等待队列中去。因为wait是个方法是吧而且还不静态方法所以它肯定是属于某个对象的,所以等待队列即这个对象的等待队列。它有点象P、V向量,就是好像大家都在等这个东西,如果你严格遵循P,V向量的机制,那么它就不会有问题。如果你乱用,程序就会乱掉。或者你不乱用,也不保证没有这个可能。另外则是,它与synchronized还是不同的,synchronized是一个非常高效的同步机制,与wait和notify完全不同。wait和notify是为了实现类似PV向量的东西而设计的。
有两种分法:
第一种是阻塞与非阻塞:sleep, wait, join都导致线程直接进入阻塞态,它们必须要经历某种“解锁”才可能得到释放即进入就绪态。而其它的如notify(使其它被阻塞在被调用对象上的线程得到释放),yield等并没有这个问题都是仅仅进入就绪态,不会被阻塞。特别是wait与join,因为你不知道你等不等得到结果,如果等不到呢是吧。所以一定要谨慎小心有计划并且聪明地使用这两个函数。
第二种就是线程调度与锁调度。线程调度指sleep, join, yield这样的方法,锁调度指wait与notify。线程调度还好做一点,相对更简单。但是锁调度非常复杂,必须要对运行时具有非常严谨的规划否则程度必将陷入假死状态,几乎是毫无疑问的。
最后,关于JAVA线程最重要的还是同步。同步才是大头中的大头。因为临界代码这个非常难找。非常非常难找。难到几乎没有办法做正确。我觉得,应该把共享变量从线程中拿掉。留下线程,拿掉共享。或者限制共享的范围。总之,程序不能这么整。这么整会乱套。所以说一定要封装好你所有的变量,不然就不可能控制多线程的共享。事情也会Go wildly.