一、線程的基本概念
線程理解:線程是一個程序里面不同的執(zhí)行路徑
每一個分支都叫做一個線程,main()叫做主分支,也叫主線程。
程只是一個靜態(tài)的概念,機器上的一個.class文件,機器上的一個.exe文件,這個叫做一個進程。程序的執(zhí)行過程都是這樣的:首先把程序的代碼放到內存的代碼區(qū)里面,代碼放到代碼區(qū)后并沒有馬上開始執(zhí)行,但這時候說明了一個進程準備開始,進程已經產生了,但還沒有開始執(zhí)行,這就是進程,所以進程其實是一個靜態(tài)的概念,它本身就不能動。平常所說的進程的執(zhí)行指的是進程里面主線程開始執(zhí)行了,也就是main()方法開始執(zhí)行了。進程是一個靜態(tài)的概念,在我們機器里面實際上運行的都是線程。
Windows操作系統(tǒng)是支持多線程的,它可以同時執(zhí)行很多個線程,也支持多進程,因此Windows操作系統(tǒng)是支持多線程多進程的操作系統(tǒng)。Linux和Uinux也是支持多線程和多進程的操作系統(tǒng)。DOS就不是支持多線程和多進程了,它只支持單進程,在同一個時間點只能有一個進程在執(zhí)行,這就叫單線程。
CPU難道真的很神通廣大,能夠同時執(zhí)行那么多程序嗎?不是的,CPU的執(zhí)行是這樣的:CPU的速度很快,一秒鐘可以算好幾億次,因此CPU把自己的時間分成一個個小時間片,我這個時間片執(zhí)行你一會,下一個時間片執(zhí)行他一會,再下一個時間片又執(zhí)行其他人一會,雖然有幾十個線程,但一樣可以在很短的時間內把他們通通都執(zhí)行一遍,但對我們人來說,CPU的執(zhí)行速度太快了,因此看起來就像是在同時執(zhí)行一樣,但實際上在一個時間點上,CPU只有一個線程在運行。
學習線程首先要理清楚三個概念:
1、進程:進程是一個靜態(tài)的概念
2、線程:一個進程里面有一個主線程叫main()方法,是一個程序里面的,一個進程里面不同的執(zhí)行路徑。
3、在同一個時間點上,一個CPU只能支持一個線程在執(zhí)行。因為CPU運行的速度很快,因此我們看起來的感覺就像是多線程一樣。
什么才是真正的多線程?如果你的機器是雙CPU,或者是雙核,這確確實實是多線程。
二、線程的創(chuàng)建和啟動
在JAVA里面,JAVA的線程是通過java.lang.Thread類來實現的,每一個Thread對象代表一個新的線程。創(chuàng)建一個新線程出來有兩種方法:第一個是從Thread類繼承,另一個是實現接口runnable。VM啟動時會有一個由主方法(public static void main())所定義的線程,這個線程叫主線程??梢酝ㄟ^創(chuàng)建Thread的實例來創(chuàng)建新的線程。你只要new一個Thread對象,一個新的線程也就出現了。每個線程都是通過某個特定的Thread對象所對應的方法run()來完成其操作的,方法run()稱為線程體。
范例1:使用實現Runnable接口創(chuàng)建和啟動新線程
開辟一個新的線程來調用run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package cn.galc.test; public class TestThread1{ public static void main(String args[]){ Runner1 r1 = new Runner1(); //這里new了一個線程類的對象出來 //r1.run();//這個稱為方法調用,方法調用的執(zhí)行是等run()方法執(zhí)行完之后才會繼續(xù)執(zhí)行main()方法 Thread t = new Thread(r1); //要啟動一個新的線程就必須new一個Thread對象出來 //這里使用的是Thread(Runnable target) 這構造方法 t.start(); //啟動新開辟的線程,新線程執(zhí)行的是run()方法,新線程與主線程會一起并行執(zhí)行 for ( int i= 0 ;i< 10 ;i++){ System.out.println( "maintheod:" +i); } } } /*定義一個類用來實現Runnable接口,實現Runnable接口就表示這個類是一個線程類*/ class Runner1 implements Runnable{ public void run(){ for ( int i= 0 ;i< 10 ;i++){ System.out.println( "Runner1:" +i); } } } |
多線程程序執(zhí)行的過程如下所示:
不開辟新線程直接調用run方法
運行結果如下:
范例2:繼承Thread類,并重寫其run()方法創(chuàng)建和啟動新的線程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package cn.galc.test; /*線程創(chuàng)建與啟動的第二種方法:定義Thread的子類并實現run()方法*/ public class TestThread2{ public static void main(String args[]){ Runner2 r2 = new Runner2(); r2.start();//調用start()方法啟動新開辟的線程 for(int i=0;i<=10;i++){ System.out.println("mainMethod:"+i); } } } /*Runner2類從Thread類繼承 通過實例化Runner2類的一個對象就可以開辟一個新的線程 調用從Thread類繼承來的start()方法就可以啟動新開辟的線程*/ class Runner2 extends Thread{ public void run(){ //重寫run()方法的實現 for ( int i= 0 ;i<= 10 ;i++){ System.out.println( "Runner2:" +i); } } } |
使用實現Runnable接口和繼承Thread類這兩種開辟新線程的方法的選擇應該優(yōu)先選擇實現Runnable接口這種方式去開辟一個新的線程。因為接口的實現可以實現多個,而類的繼承只能是單繼承。因此在開辟新線程時能夠使用Runnable接口就盡量不要使用從Thread類繼承的方式來開辟新的線程。
三、線程狀態(tài)轉換
3.1.線程控制的基本方法
3.2. sleep/join/yield方法介紹
sleep方法的應用范例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
package cn.galc.test; import java.util.*; public class TestThread3 { public static void main(String args[]){ MyThread thread = new MyThread(); thread.start(); //調用start()方法啟動新開辟的線程 try { /*Thread.sleep(10000); sleep()方法是在Thread類里面聲明的一個靜態(tài)方法,因此可以使用Thread.sleep()的格式進行調用 */ /*MyThread.sleep(10000); MyThread類繼承了Thread類,自然也繼承了sleep()方法,所以也可以使用MyThread.sleep()的格式進行調用 */ /*靜態(tài)方法的調用可以直接使用“類名.靜態(tài)方法名” 或者“對象的引用.靜態(tài)方法名”的方式來調用*/ MyThread.sleep(10000); System.out.println("主線程睡眠了10秒種后再次啟動了"); //在main()方法里面調用另外一個類的靜態(tài)方法時,需要使用“靜態(tài)方法所在的類.靜態(tài)方法名”這種方式來調用 /* 所以這里是讓主線程睡眠10秒種 在哪個線程里面調用了sleep()方法就讓哪個線程睡眠,所以現在是主線程睡眠了。 */ } catch (InterruptedException e) { e.printStackTrace(); } //thread.interrupt();//使用interrupt()方法去結束掉一個線程的執(zhí)行并不是一個很好的做法 thread.flag=false;//改變循環(huán)條件,結束死循環(huán) /** * 當發(fā)生InterruptedException時,直接把循環(huán)的條件設置為false即可退出死循環(huán), * 繼而結束掉子線程的執(zhí)行,這是一種比較好的結束子線程的做法 */ /** * 調用interrupt()方法把正在運行的線程打斷 相當于是主線程一盆涼水潑上去把正在執(zhí)行分線程打斷了 分線程被打斷之后就會拋InterruptedException異常,這樣就會執(zhí)行return語句返回,結束掉線程的執(zhí)行 所以這里的分線程在執(zhí)行完10秒鐘之后就結束掉了線程的執(zhí)行 */ } } class MyThread extends Thread { boolean flag = true;// 定義一個標記,用來控制循環(huán)的條件 public void run() { /* * 注意:這里不能在run()方法的后面直接寫throw Exception來拋異常, * 因為現在是要重寫從Thread類繼承而來的run()方法,重寫方法不能拋出比被重寫的方法的不同的異常。 * 所以這里只能寫try……catch()來捕獲異常 */ while (flag) { System.out.println("==========" + new Date().toLocaleString() + "==========="); try { /* * 靜態(tài)方法的調用格式一般為“類名.方法名”的格式去調用 在本類中聲明的靜態(tài)方法時調用時直接寫靜態(tài)方法名即可。 當然使用“類名.方法名”的格式去調用也是沒有錯的 */ // MyThread.sleep(1000);//使用“類名.方法名”的格式去調用屬于本類的靜態(tài)方法 sleep(1000);//睡眠的時如果被打斷就會拋出InterruptedException異常 // 這里是讓這個新開辟的線程每隔一秒睡眠一次,然后睡眠一秒鐘后再次啟動該線程 // 這里在一個死循環(huán)里面每隔一秒啟動一次線程,每個一秒打印出當前的系統(tǒng)時間 } catch (InterruptedException e) { /* * 睡眠的時一盤冷水潑過來就有可能會打斷睡眠 * 因此讓正在運行線程被一些意外的原因中斷的時候有可能會拋被打擾中斷(InterruptedException)的異常 */ return ; // 線程被中斷后就返回,相當于是結束線程 } } } } |
運行結果:
join方法的使用范例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package cn.galc.test; public class TestThread4 { public static void main(String args[]) { MyThread2 thread2 = new MyThread2( "mythread" ); // 在創(chuàng)建一個新的線程對象的同時給這個線程對象命名為mythread thread2.start(); // 啟動線程 try { thread2.join(); // 調用join()方法合并線程,將子線程mythread合并到主線程里面 // 合并線程后,程序的執(zhí)行的過程就相當于是方法的調用的執(zhí)行過程 } catch (InterruptedException e) { e.printStackTrace(); } for ( int i = 0 ; i <= 5 ; i++) { System.out.println( "I am main Thread" ); } } } class MyThread2 extends Thread { MyThread2(String s) { super (s); /* * 使用super關鍵字調用父類的構造方法 * 父類Thread的其中一個構造方法:“public Thread(String name)” * 通過這樣的構造方法可以給新開辟的線程命名,便于管理線程 */ } public void run() { for ( int i = 1 ; i <= 5 ; i++) { System.out.println( "I am a\t" + getName()); // 使用父類Thread里面定義的 //public final String getName(),Returns this thread's name. try { sleep( 1000 ); // 讓子線程每執(zhí)行一次就睡眠1秒鐘 } catch (InterruptedException e) { return ; } } } } |
運行結果:
yield方法的使用范例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package cn.galc.test; public class TestThread5 { public static void main(String args[]) { MyThread3 t1 = new MyThread3( "t1" ); /* 同時開辟了兩條子線程t1和t2,t1和t2執(zhí)行的都是run()方法 */ /* 這個程序的執(zhí)行過程中總共有3個線程在并行執(zhí)行,分別為子線程t1和t2以及主線程 */ MyThread3 t2 = new MyThread3("t2"); t1.start();// 啟動子線程t1 t2.start();// 啟動子線程t2 for (int i = 0; i <= 5; i++) { System.out.println("I am main Thread"); } } } class MyThread3 extends Thread { MyThread3(String s) { super(s); } public void run() { for (int i = 1; i <= 5; i++) { System.out.println(getName() + ":" + i); if (i % 2 == 0) { yield();// 當執(zhí)行到i能被2整除時當前執(zhí)行的線程就讓出來讓另一個在執(zhí)行run()方法的線程來優(yōu)先執(zhí)行 /* * 在程序的運行的過程中可以看到, * 線程t1執(zhí)行到(i%2==0)次時就會讓出線程讓t2線程來優(yōu)先執(zhí)行 * 而線程t2執(zhí)行到(i%2==0)次時也會讓出線程給t1線程優(yōu)先執(zhí)行 */ } } } } |
運行結果如下:
以上就是本文的全部內容,對java線程進行全面學習,希望可以幫助到大家。