1, Java中编写多线程程序和其他的编程语言相比容易很多。主要通过Runnable接口和Thread类来实现。
publicclassSimpleRunnable implements Runnable{ private String message; publicstaticvoid main(String[] args) { SimpleRunnabler1 = new SimpleRunnable("Hello"); Threadt1 = new Thread(r1); t1.start(); for(;;){ System.out.println("Bye-bye"); } } public SimpleRunnable(String message){ this.message = message; } @Override publicvoid run() { for(;;){ System.out.println(message); } } }
以上,通过继承于Runable接口实现run()来实现多线程,通过Thread来实现虚拟CPU,让线程r1在独立的线程中运行。事实上,该线程和主线程并不是完全的独立运行的线程,而是r1线程和主线程轮番切换运行,只是切换的运行速度特别快,看上去像是两个线程独立运行。
2, 线程的切换,根据不同的操作系统而有所不同:
一种是排队机制,如果一个线程抢得了CPU,直到该线程运行结束,或者被动停止才推出。
一种是通过优先级模式,优先级最高的线程优先抢到CPU。
一种是时间片,每一个线程拥有公平的时间片,当一个线程运行完时间片后退出,其他的线程拥有公平的机会抢占CPU。
Java的线程采用基于优先级和时间片综合的方式,具体实现根据运行的操作系统而有所不同。比如说当一个线程时间片运行完后退出并将优先级降一级,然后排序优先级比较高的线程抢占CPU,如果运行的过程中来了一个优先级更高的线程,则会抢占当前的CPU。
3, 线程运行状态:
线程停止(Pause)包括,sleep(),wait(),suspend(),I/O blocking
线程重启(Run)包括,sleep time-out,notify(),resume(),I/O finished
线程终止,stop()
线程等待,join()
线程主动让出CPU,yield()
以上suspend(),resume(),stop()都已过时,不再建议使用。
4, 其中sleep()函数是static的,是属于类的函数,而不是对象。该函数将使调用的线程暂停一定时间,而不是某一个其他的线程停止。如下
publicstaticvoidmain(String[] args)throwsException { SimpleRunnabler1 = new SimpleRunnable("Hello"); Threadt1 = newThread(r1); t1.start(); for(inti = 0; i < 10; i++){ System.out.println("Bye-bye"); } t1.sleep(10000); t1.stop(); }
以上代码是使主线程停止10秒钟,而不是使t1停止10秒钟。另外sleep()使线程暂停的时间是个模糊值,以上理论将使主线程暂停10秒钟,但是由于线程切换的不确定性,导致主线程并不是精确的暂停10秒钟。
5, 另外一个static的函数是yield(),他表示当前正在运行的线程主动让出CPU,该函数也是仅仅能作用于当前线程类似于以上sleep()函数。
6, 编程是为了追求高效的结果,采用多线程。但是多线程运行是很难预测的,因此我们会采取一定的方法,让多线程的运行结果尽量可预测。
class Thread1 extends Thread{ @Override publicvoid run(){ try{ System.out.println("thread1 start"); sleep(10000); // <3> System.out.println("thread1 finish");//<4> }catch(Exception e){ } } } class Thread2 extends Thread{ @Override publicvoid run(){ try{ System.out.println("thread2 start"); suspend(); // <2> System.out.println("thread2 finish");// <7> }catch(Exception e){ } } } publicclassThreadTest { publicstaticvoid main(String[] args) { Threadt1 = new Thread1(); Thread2t2 = new Thread2(); t1.start(); t2.start(); try{ System.out.println("s1"); t1.join(); // <1> System.out.println("s2"); // <5> t2.resume(); //<6> System.out.println("s3"); // <8> t2.join(); // <9> }catch(Exception e){ } System.out.println("s4"); // <10> } }
以上运行其中一次的运行结果为:
thread1 start
thread2 start
s1
thread1 finish
s2
s3
thread2 finish
s4
但是并不是每次都得到这样的结果。但是也有一部分是可预测的。哪些是可预测的?哪些是不可预测的?
(1) 前三行一定会占据前三行,由于f35d6e602fd7d0f0edfa6f7d103c1b57的代码使主线程停止,等待线程t1运行结束,而t1线程在5bdf4c78156c7953567bb5a0aef2fc53处使线程暂停10秒钟,同时2cc198a1d5eb0d3eb508d858c9f5cbdb处代码是线程t2处于挂起状态,知道有代码唤醒该线程,否则该线程就会一直处于阻塞状态。但是前三行的输出顺序是不可预测的。
(2) 第四行一定会在第四行,当t1休眠结束后,首先运行23889872c2e8594e0f446a471a78ec4c处代码
(3) 第五行一定会在第五行,当t1线程运行结束后,主线程获得CPU开始运行代码43ad812d3a971134e40facaca816c822
(4) 第六行和第七行的位置可能会互换,当代码efbfa0de8737dc86eae413541a49df20唤醒线程t2后从40107655ec554331c1c6222ab67a141c开始运行,同时主线程继续运行37cd6113a8c348d99fa846f2c6fcea98
(5) 最后一行一定会在最后一行。在c161494dba5e0dd0fb25d890c74e408d暂停主线程,等待t2运行结束,运行eebe431eeb58984ec8915354762c30c6输出最后一行,主线程结束退出。