java 多線程基礎

1、線程的基本概念

  

線程理解:線程是一個程序裏面不一樣的執行路徑java

  每個分支都叫作一個線程,main()叫作主分支,也叫主線程。小程序

  程只是一個靜態的概念,機器上的一個.class文件,機器上的一個.exe文件,這個叫作一個進程。程序的執行過程都是這樣的:首先把程序的代碼放到內存的代碼區裏面,代碼放到代碼區後並無立刻開始執行,但這時候說明了一個進程準備開始,進程已經產生了,但尚未開始執行,這就是進程,因此進程實際上是一個靜態的概念,它自己就不能動。日常所說的進程的執行指的是進程裏面主線程開始執行了,也就是main()方法開始執行了。進程是一個靜態的概念,在咱們機器裏面實際上運行的都是線程。多線程

  Windows操做系統是支持多線程的,它能夠同時執行不少個線程,也支持多進程,所以Windows操做系統是支持多線程多進程的操做系統。Linux和Uinux也是支持多線程和多進程的操做系統。DOS就不是支持多線程和多進程了,它只支持單進程,在同一個時間點只能有一個進程在執行,這就叫單線程dom

  CPU難道真的很神通廣大,可以同時執行那麼多程序嗎?不是的,CPU的執行是這樣的:CPU的速度很快,一秒鐘能夠算好幾億次,所以CPU把本身的時間分紅一個個小時間片,我這個時間片執行你一會,下一個時間片執行他一會,再下一個時間片又執行其餘人一會,雖然有幾十個線程,但同樣能夠在很短的時間內把他們統統都執行一遍,但對咱們人來講,CPU的執行速度太快了,所以看起來就像是在同時執行同樣,但實際上在一個時間點上,CPU只有一個線程在運行。學習

學習線程首先要理清楚三個概念:this

  1. 進程:進程是一個靜態的概念
  2. 線程:一個進程裏面有一個主線程叫main()方法,是一個程序裏面的,一個進程裏面不一樣的執行路徑。
  3. 在同一個時間點上,一個CPU只能支持一個線程在執行。由於CPU運行的速度很快,所以咱們看起來的感受就像是多線程同樣。

  什麼纔是真正的多線程?若是你的機器是雙CPU,或者是雙核,這確確實實是多線程。spa

 

 

2、線程的建立和啓動

  

  在JAVA裏面,JAVA的線程是經過java.lang.Thread類來實現的,每個Thread對象表明一個新的線程。建立一個新線程出來有兩種方法:第一個是從Thread類繼承,另外一個是實現接口runnable。VM啓動時會有一個由主方法(public static void main())所定義的線程,這個線程叫主線程。能夠經過建立Thread的實例來建立新的線程。你只要new一個Thread對象,一個新的線程也就出現了。每一個線程都是經過某個特定的Thread對象所對應的方法run()來完成其操做的,方法run()稱爲線程體。操作系統

範例1:使用實現Runnable接口建立和啓動新線程線程

開闢一個新的線程來調用run方法code

複製代碼
 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、線程狀態轉換

  

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、線程的優先級別

  

線程優先級別的使用範例:

複製代碼
 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()方法一結束,線程也就結束了。

2、線程同步

  

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 }
複製代碼
相關文章
相關標籤/搜索