2019獨角獸企業重金招聘Python工程師標準>>>
一、線程的基本概念
線程理解:線程是一個程序里面不同的執行路徑
每一個分支都叫做一個線程,main()叫做主分支,也叫主線程。
程只是一個靜態的概念,機器上的一個.class文件,機器上的一個.exe文件,這個叫做一個進程。程序的執行過程都是這樣的:首先把程序的代碼放到內存的代碼區里面,代碼放到代碼區后并沒有馬上開始執行,但這時候說明了一個進程準備開始,進程已經產生了,但還沒有開始執行,這就是進程,所以進程其實是一個靜態的概念,它本身就不能動。平常所說的進程的執行指的是進程里面主線程開始執行了,也就是main()方法開始執行了。進程是一個靜態的概念,在我們機器里面實際上運行的都是線程。
Windows操作系統是支持多線程的,它可以同時執行很多個線程,也支持多進程,因此Windows操作系統是支持多線程多進程的操作系統。Linux和Uinux也是支持多線程和多進程的操作系統。DOS就不是支持多線程和多進程了,它只支持單進程,在同一個時間點只能有一個進程在執行,這就叫單線程。
CPU難道真的很神通廣大,能夠同時執行那么多程序嗎?不是的,CPU的執行是這樣的:CPU的速度很快,一秒鐘可以算好幾億次,因此CPU把自己的時間分成一個個小時間片,我這個時間片執行你一會,下一個時間片執行他一會,再下一個時間片又執行其他人一會,雖然有幾十個線程,但一樣可以在很短的時間內把他們通通都執行一遍,但對我們人來說,CPU的執行速度太快了,因此看起來就像是在同時執行一樣,但實際上在一個時間點上,CPU只有一個線程在運行。
學習線程首先要理清楚三個概念:
- 進程:進程是一個靜態的概念
- 線程:一個進程里面有一個主線程叫main()方法,是一個程序里面的,一個進程里面不同的執行路徑。
- 在同一個時間點上,一個CPU只能支持一個線程在執行。因為CPU運行的速度很快,因此我們看起來的感覺就像是多線程一樣。
什么才是真正的多線程?如果你的機器是雙CPU,或者是雙核,這確確實實是多線程。
二、線程的創建和啟動
在JAVA里面,JAVA的線程是通過java.lang.Thread類來實現的,每一個Thread對象代表一個新的線程。創建一個新線程出來有兩種方法:第一個是從Thread類繼承,另一個是實現接口runnable。VM啟動時會有一個由主方法(public?static?void?main())所定義的線程,這個線程叫主線程。可以通過創建Thread的實例來創建新的線程。你只要new一個Thread對象,一個新的線程也就出現了。每個線程都是通過某個特定的Thread對象所對應的方法run()來完成其操作的,方法run()稱為線程體。
范例1:使用實現Runnable接口創建和啟動新線程
開辟一個新的線程來調用run方法
1 package cn.galc.test;
2
3 public class TestThread1{
4 public static void main(String args[]){
5 Runner1 r1 = new Runner1();//這里new了一個線程類的對象出來
6 //r1.run();//這個稱為方法調用,方法調用的執行是等run()方法執行完之后才會繼續執行main()方法
7 Thread t = new Thread(r1);//要啟動一個新的線程就必須new一個Thread對象出來
8 //這里使用的是Thread(Runnable target) 這構造方法
9 t.start();//啟動新開辟的線程,新線程執行的是run()方法,新線程與主線程會一起并行執行
10 for(int i=0;i<10;i++){
11 System.out.println("maintheod:"+i);
12 }
13 }
14 }
15 /*定義一個類用來實現Runnable接口,實現Runnable接口就表示這個類是一個線程類*/
16 class Runner1 implements Runnable{
17 public void run(){
18 for(int i=0;i<10;i++){
19 System.out.println("Runner1:"+i);
20 }
21 }
22 }
多線程程序執行的過程如下所示:
不開辟新線程直接調用run方法
運行結果如下:
?范例2:繼承Thread類,并重寫其run()方法創建和啟動新的線程
1 package cn.galc.test;
2
3 /*線程創建與啟動的第二種方法:定義Thread的子類并實現run()方法*/
4 public class TestThread2{
5 public static void main(String args[]){
6 Runner2 r2 = new Runner2();
7 r2.start();//調用start()方法啟動新開辟的線程
8 for(int i=0;i<=10;i++){
9 System.out.println("mainMethod:"+i);
10 }
11 }
12 }
13 /*Runner2類從Thread類繼承
14 通過實例化Runner2類的一個對象就可以開辟一個新的線程
15 調用從Thread類繼承來的start()方法就可以啟動新開辟的線程*/
16 class Runner2 extends Thread{
17 public void run(){//重寫run()方法的實現
18 for(int i=0;i<=10;i++){
19 System.out.println("Runner2:"+i);
20 }
21 }
22 }
使用實現Runnable接口和繼承Thread類這兩種開辟新線程的方法的選擇應該優先選擇實現Runnable接口這種方式去開辟一個新的線程。因為接口的實現可以實現多個,而類的繼承只能是單繼承。因此在開辟新線程時能夠使用Runnable接口就盡量不要使用從Thread類繼承的方式來開辟新的線程。
三、線程狀態轉換
3.1.線程控制的基本方法
3.2. sleep/join/yield方法介紹
sleep方法的應用范例:
1 package cn.galc.test;
2
3 import java.util.*;
4
5 public class TestThread3 {
6 public static void main(String args[]){
7 MyThread thread = new MyThread();
8 thread.start();//調用start()方法啟動新開辟的線程
9 try {
10 /*Thread.sleep(10000);
11 sleep()方法是在Thread類里面聲明的一個靜態方法,因此可以使用Thread.sleep()的格式進行調用
12 */
13 /*MyThread.sleep(10000);
14 MyThread類繼承了Thread類,自然也繼承了sleep()方法,所以也可以使用MyThread.sleep()的格式進行調用
15 */
16 /*靜態方法的調用可以直接使用“類名.靜態方法名”
17 或者“對象的引用.靜態方法名”的方式來調用*/
18 MyThread.sleep(10000);
19 System.out.println("主線程睡眠了10秒種后再次啟動了");
20 //在main()方法里面調用另外一個類的靜態方法時,需要使用“靜態方法所在的類.靜態方法名”這種方式來調用
21 /*
22 所以這里是讓主線程睡眠10秒種
23 在哪個線程里面調用了sleep()方法就讓哪個線程睡眠,所以現在是主線程睡眠了。
24 */
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }
28 //thread.interrupt();//使用interrupt()方法去結束掉一個線程的執行并不是一個很好的做法
29 thread.flag=false;//改變循環條件,結束死循環
30 /**
31 * 當發生InterruptedException時,直接把循環的條件設置為false即可退出死循環,
32 * 繼而結束掉子線程的執行,這是一種比較好的結束子線程的做法
33 */
34 /**
35 * 調用interrupt()方法把正在運行的線程打斷
36 相當于是主線程一盆涼水潑上去把正在執行分線程打斷了
37 分線程被打斷之后就會拋InterruptedException異常,這樣就會執行return語句返回,結束掉線程的執行
38 所以這里的分線程在執行完10秒鐘之后就結束掉了線程的執行
39 */
40 }
41 }
42
43 class MyThread extends Thread {
44 boolean flag = true;// 定義一個標記,用來控制循環的條件
45
46 public void run() {
47 /*
48 * 注意:這里不能在run()方法的后面直接寫throw Exception來拋異常,
49 * 因為現在是要重寫從Thread類繼承而來的run()方法,重寫方法不能拋出比被重寫的方法的不同的異常。
50 * 所以這里只能寫try……catch()來捕獲異常
51 */
52 while (flag) {
53 System.out.println("==========" + new Date().toLocaleString() + "===========");
54 try {
55 /*
56 * 靜態方法的調用格式一般為“類名.方法名”的格式去調用 在本類中聲明的靜態方法時調用時直接寫靜態方法名即可。 當然使用“類名.方法名”的格式去調用也是沒有錯的
57 */
58 // MyThread.sleep(1000);//使用“類名.方法名”的格式去調用屬于本類的靜態方法
59 sleep(1000);//睡眠的時如果被打斷就會拋出InterruptedException異常
60 // 這里是讓這個新開辟的線程每隔一秒睡眠一次,然后睡眠一秒鐘后再次啟動該線程
61 // 這里在一個死循環里面每隔一秒啟動一次線程,每個一秒打印出當前的系統時間
62 } catch (InterruptedException e) {
63 /*
64 * 睡眠的時一盤冷水潑過來就有可能會打斷睡眠
65 * 因此讓正在運行線程被一些意外的原因中斷的時候有可能會拋被打擾中斷(InterruptedException)的異常
66 */
67 return;
68 // 線程被中斷后就返回,相當于是結束線程
69 }
70 }
71 }
72 }
運行結果:
?join方法的使用范例:
1 package cn.galc.test;
2
3 public class TestThread4 {
4 public static void main(String args[]) {
5 MyThread2 thread2 = new MyThread2("mythread");
6 // 在創建一個新的線程對象的同時給這個線程對象命名為mythread
7 thread2.start();// 啟動線程
8 try {
9 thread2.join();// 調用join()方法合并線程,將子線程mythread合并到主線程里面
10 // 合并線程后,程序的執行的過程就相當于是方法的調用的執行過程
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 for (int i = 0; i <= 5; i++) {
15 System.out.println("I am main Thread");
16 }
17 }
18 }
19
20 class MyThread2 extends Thread {
21 MyThread2(String s) {
22 super(s);
23 /*
24 * 使用super關鍵字調用父類的構造方法
25 * 父類Thread的其中一個構造方法:“public Thread(String name)”
26 * 通過這樣的構造方法可以給新開辟的線程命名,便于管理線程
27 */
28 }
29
30 public void run() {
31 for (int i = 1; i <= 5; i++) {
32 System.out.println("I am a\t" + getName());
33 // 使用父類Thread里面定義的
34 //public final String getName(),Returns this thread's name.
35 try {
36 sleep(1000);// 讓子線程每執行一次就睡眠1秒鐘
37 } catch (InterruptedException e) {
38 return;
39 }
40 }
41 }
42 }
運行結果:
yield方法的使用范例:
1 package cn.galc.test;
2
3 public class TestThread5 {
4 public static void main(String args[]) {
5 MyThread3 t1 = new MyThread3("t1");
6 /* 同時開辟了兩條子線程t1和t2,t1和t2執行的都是run()方法 */
7 /* 這個程序的執行過程中總共有3個線程在并行執行,分別為子線程t1和t2以及主線程 */
8 MyThread3 t2 = new MyThread3("t2");
9 t1.start();// 啟動子線程t1
10 t2.start();// 啟動子線程t2
11 for (int i = 0; i <= 5; i++) {
12 System.out.println("I am main Thread");
13 }
14 }
15 }
16
17 class MyThread3 extends Thread {
18 MyThread3(String s) {
19 super(s);
20 }
21
22 public void run() {
23 for (int i = 1; i <= 5; i++) {
24 System.out.println(getName() + ":" + i);
25 if (i % 2 == 0) {
26 yield();// 當執行到i能被2整除時當前執行的線程就讓出來讓另一個在執行run()方法的線程來優先執行
27 /*
28 * 在程序的運行的過程中可以看到,
29 * 線程t1執行到(i%2==0)次時就會讓出線程讓t2線程來優先執行
30 * 而線程t2執行到(i%2==0)次時也會讓出線程給t1線程優先執行
31 */
32 }
33 }
34 }
35 }
運行結果如下:
?
一、線程的優先級別
線程優先級別的使用范例:
1 package cn.galc.test;
2
3 public class TestThread6 {
4 public static void main(String args[]) {
5 MyThread4 t4 = new MyThread4();
6 MyThread5 t5 = new MyThread5();
7 Thread t1 = new Thread(t4);
8 Thread t2 = new Thread(t5);
9 t1.setPriority(Thread.NORM_PRIORITY + 3);// 使用setPriority()方法設置線程的優先級別,這里把t1線程的優先級別進行設置
10 /*
11 * 把線程t1的優先級(priority)在正常優先級(NORM_PRIORITY)的基礎上再提高3級
12 * 這樣t1的執行一次的時間就會比t2的多很多
13 * 默認情況下NORM_PRIORITY的值為5
14 */
15 t1.start();
16 t2.start();
17 System.out.println("t1線程的優先級是:" + t1.getPriority());
18 // 使用getPriority()方法取得線程的優先級別,打印出t1的優先級別為8
19 }
20 }
21
22 class MyThread4 implements Runnable {
23 public void run() {
24 for (int i = 0; i <= 1000; i++) {
25 System.out.println("T1:" + i);
26 }
27 }
28 }
29
30 class MyThread5 implements Runnable {
31 public void run() {
32 for (int i = 0; i <= 1000; i++) {
33 System.out.println("===============T2:" + i);
34 }
35 }
36 }
run()方法一結束,線程也就結束了。
二、線程同步
synchronized關鍵字的使用范例:
1 package cn.galc.test;
2
3 public class TestSync implements Runnable {
4 Timer timer = new Timer();
5
6 public static void main(String args[]) {
7 TestSync test = new TestSync();
8 Thread t1 = new Thread(test);
9 Thread t2 = new Thread(test);
10 t1.setName("t1");// 設置t1線程的名字
11 t2.setName("t2");// 設置t2線程的名字
12 t1.start();
13 t2.start();
14 }
15
16 public void run() {
17 timer.add(Thread.currentThread().getName());
18 }
19 }
20
21 class Timer {
22 private static int num = 0;
23
24 public/* synchronized */void add(String name) {// 在聲明方法時加入synchronized時表示在執行這個方法的過程之中當前對象被鎖定
25 synchronized (this) {
26 /*
27 * 使用synchronized(this)來鎖定當前對象,這樣就不會再出現兩個不同的線程同時訪問同一個對象資源的問題了 只有當一個線程訪問結束后才會輪到下一個線程來訪問
28 */
29 num++;
30 try {
31 Thread.sleep(1);
32 } catch (InterruptedException e) {
33 e.printStackTrace();
34 }
35 System.out.println(name + ":你是第" + num + "個使用timer的線程");
36 }
37 }
38 }
線程死鎖的問題:
1 package cn.galc.test;
2
3 /*這個小程序模擬的是線程死鎖的問題*/
4 public class TestDeadLock implements Runnable {
5 public int flag = 1;
6 static Object o1 = new Object(), o2 = new Object();
7
8 public void run() {
9 System.out.println(Thread.currentThread().getName() + "的flag=" + flag);
10 /*
11 * 運行程序后發現程序執行到這里打印出flag以后就再也不往下執行后面的if語句了
12 * 程序也就死在了這里,既不往下執行也不退出
13 */
14
15 /* 這是flag=1這個線程 */
16 if (flag == 1) {
17 synchronized (o1) {
18 /* 使用synchronized關鍵字把對象01鎖定了 */
19 try {
20 Thread.sleep(500);
21 } catch (InterruptedException e) {
22 e.printStackTrace();
23 }
24 synchronized (o2) {
25 /*
26 * 前面已經鎖住了對象o1,只要再能鎖住o2,那么就能執行打印出1的操作了
27 * 可是這里無法鎖定對象o2,因為在另外一個flag=0這個線程里面已經把對象o1給鎖住了
28 * 盡管鎖住o2這個對象的線程會每隔500毫秒睡眠一次,可是在睡眠的時候仍然是鎖住o2不放的
29 */
30 System.out.println("1");
31 }
32 }
33 }
34 /*
35 * 這里的兩個if語句都將無法執行,因為已經造成了線程死鎖的問題
36 * flag=1這個線程在等待flag=0這個線程把對象o2的鎖解開,
37 * 而flag=0這個線程也在等待flag=1這個線程把對象o1的鎖解開
38 * 然而這兩個線程都不愿意解開鎖住的對象,所以就造成了線程死鎖的問題
39 */
40
41 /* 這是flag=0這個線程 */
42 if (flag == 0) {
43 synchronized (o2) {
44 /* 這里先使用synchronized鎖住對象o2 */
45 try {
46 Thread.sleep(500);
47 } catch (InterruptedException e) {
48 e.printStackTrace();
49 }
50 synchronized (o1) {
51 /*
52 * 前面已經鎖住了對象o2,只要再能鎖住o1,那么就能執行打印出0的操作了 可是這里無法鎖定對象o1,因為在另外一個flag=1這個線程里面已經把對象o1給鎖住了 盡管鎖住o1這個對象的線程會每隔500毫秒睡眠一次,可是在睡眠的時候仍然是鎖住o1不放的
53 */
54 System.out.println("0");
55 }
56 }
57 }
58 }
59
60 public static void main(String args[]) {
61 TestDeadLock td1 = new TestDeadLock();
62 TestDeadLock td2 = new TestDeadLock();
63 td1.flag = 1;
64 td2.flag = 0;
65 Thread t1 = new Thread(td1);
66 Thread t2 = new Thread(td2);
67 t1.setName("線程td1");
68 t2.setName("線程td2");
69 t1.start();
70 t2.start();
71 }
72 }
解決線程死鎖的問題最好只鎖定一個對象,不要同時鎖定兩個對象
生產者消費者問題:
1 package cn.galc.test;
2
3 /* 范例名稱:生產者--消費者問題
4 * 源文件名稱:ProducerConsumer.java
5 * 要 點:
6 * 1. 共享數據的不一致性/臨界資源的保護
7 * 2. Java對象鎖的概念
8 * 3. synchronized關鍵字/wait()及notify()方法
9 */
10
11 public class ProducerConsumer {
12 public static void main(String args[]){
13 SyncStack stack = new SyncStack();
14 Runnable p=new Producer(stack);
15 Runnable c = new Consumer(stack);
16 Thread p1 = new Thread(p);
17 Thread c1 = new Thread(c);
18
19 p1.start();
20 c1.start();
21 }
22 }
23
24
25 class SyncStack{ //支持多線程同步操作的堆棧的實現
26 private int index = 0;
27 private char []data = new char[6];
28 public synchronized void push(char c){
29 if(index == data.length){
30 try{
31 this.wait();
32 }catch(InterruptedException e){}
33 }
34 this.notify();
35 data[index] = c;
36 index++;
37 }
38 public synchronized char pop(){
39 if(index ==0){
40 try{
41 this.wait();
42 }catch(InterruptedException e){}
43 }
44 this.notify();
45 index--;
46 return data[index];
47 }
48 }
49
50
51 class Producer implements Runnable{
52 SyncStack stack;
53 public Producer(SyncStack s){
54 stack = s;
55 }
56 public void run(){
57 for(int i=0; i<20; i++){
58 char c =(char)(Math.random()*26+'A');
59 stack.push(c);
60 System.out.println("produced:"+c);
61 try{
62 Thread.sleep((int)(Math.random()*1000));
63 }catch(InterruptedException e){
64 }
65 }
66 }
67 }
68
69
70 class Consumer implements Runnable{
71 SyncStack stack;
72 public Consumer(SyncStack s){
73 stack = s;
74 }
75 public void run(){
76 for(int i=0;i<20;i++){
77 char c = stack.pop();
78 System.out.println("消費:"+c);
79 try{
80 Thread.sleep((int)(Math.random()*1000));
81 }catch(InterruptedException e){
82 }
83 }
84 }
85 }