多線程詳解和代碼測試


1:多線程
(1)多線程:一個應用程序有多條執行路徑
進程:正在執行的應用程序。

             是系統進行資源分配和調用的獨立單元。每一個進程都有他自己的內存空間和系統資源
線程:進程的執行單元,執行路徑。

              在同一個進程內又可以執行多個任務,而這每一個任務就可以視為一個線程。
單線程:一個應用程序只有一條執行路徑
多線程:一個應用程序有多條執行路徑

多進程的意義?
提高CPU的使用率


多線程的意義?
提高應用程序的使用率


(2)Java程序的運行原理及JVM的啟動是多線程的嗎?
A:Java命令去啟動JVM,JVM會啟動一個進程,該進程會啟動一個主線程。
B:JVM的啟動是多線程的,因為它最低有兩個線程啟動了,主線程和垃圾回收線程。

(3)多線程的實現方案
A:繼承Thread類

 1 /*
 2  * java提供兩兩種方式實現多線程程序
 3  * 
 4  * 方式一:繼承Thred類
 5  * 步驟:自定義MyThread類 繼承Thred類 
 6  *     MyThread類里面重寫run()?
 7  *        這里要明白為什么是run方法
 8  *     創建對象
 9  *     啟動線程
10  *     
11  */
12 public class MyThreadDemo {
13     public static void main(String[] args) {
14         //創建線程對象
15         //MyThread my = new MyThread();
16         //啟動線程
17         //my.run();
18         //my.run();
19         /*
20          * 調用run()方法為什么是單線程的?
21          * 由於run()方法直接調用其實就相當於普通的方法調用,所有是單線程的效果
22          * 可以通過start()方法來實現多線程
23          * 
24          * 這里要明白run()和start()的區別:
25          * run():僅是封裝被線程執行的代碼,直接調用屬於普通方法
26          * start():先啟動線程,然后再通過jvm去調用該線程的run()方法
27          */
28         
29         //創建兩個線程
30         MyThread my1 = new MyThread();
31         MyThread my2 = new MyThread();
32         my1.start();
33         my2.start();
34     }
35 
36 }

 

 1 /*
 2  * 這個類要重寫run()方法,是因為,不是說類中的所有代碼都需要被線程執行。
 3  * 這時為了區分哪些代碼能夠被線程執行,Java就提供了Thread類中的run()方法
 4  * 來包含哪些需要被線程執行的代碼
 5  */
 6 public class MyThread extends Thread{
 7     @Override
 8     public void run(){
 9         for(int x = 0; x < 100; x++){
10             System.out.println(x);
11         }
12     }
13 
14 }

 

獲取線程名稱

 1 /*
 2  * 獲取線程對象名稱
 3  */
 4 public class MyThredDemo1 {
 5     public static void main(String[] args) {
 6         //帶參構造方法給線程起名字
 7         MyThread1 my1 = new MyThread1("a");
 8         MyThread1 my2 = new MyThread1("b");
 9         
10         //調用方法設置名稱
11         //my1.setName("a");
12         //my2.setName("b");
13         
14         my1.start();
15         my2.start();
16         
17         //獲取main方法所在線程名稱
18         System.out.println(Thread.currentThread().getName());
19     }
20 
21 }

 

 1 public class MyThread1 extends Thread{
 2 
 3     public  MyThread1() {
 4         
 5     }
 6     
 7     public  MyThread1(String name) {
 8         
 9     }
10     
11     @Override
12     public void run(){
13         for(int x = 0; x < 100; x++){
14             System.out.println(getName() + ":" + x);
15         }
16     }
17 }

 


B:實現Runnable接口(一般都會采用方式二)

 1 /*
 2  * 多線程實現方式2:實現Runnable接口
 3  * 步驟:1.自定義類MyRunnable實現Runnable接口
 4  *     2.重寫run()方法
 5  *     3.創建MyRunnable類的對象
 6  *     4.創建Thread類的對象,並把3步驟的對象作為構造參數傳遞
 7  */
 8 public class MyRunnableDemo {
 9      public static void main(String[] args) {
10          //創建MyRunnable類的對象
11         MyRunnable my = new MyRunnable();
12         
13         //創建Thread類的對象,並把3步驟的對象作為構造參數傳遞
14         //Thread(Runnale target)
15         //Thread t1 = new Thread(my);
16         //Thread t2 = new Thread(my);
17         //t1.setName("aa");
18         //t2.setName("bb");
19         
20         //Thread(Runnable target, String name) 另一種方法命名
21         Thread t1 = new Thread(my, "aa");
22         Thread t2 = new Thread(my, "bb");
23         
24         t1.start();
25         t2.start();
26    }
27 }
 1 public class MyRunnable implements Runnable {
 2 /*
 3  * 1.自定義類MyRunnable實現Runnable接口
 4  * 2.重寫run()方法
 5  * 
 6  */
 7     @Override
 8     public void run() {
 9         for(int x = 0; x < 100; x++){
10             //由於實現接口的方法不能直接Thread類的方法,這時需要間接使用
11             System.out.println(Thread.currentThread().getName() + ":" + x);
12         }
13 
14     }
15 
16 }

 

小結:

實現多線程的方式:兩種

方式一:繼承Thread類

步驟:1.自定義類MyThread繼承Thread類

           2.在MyThread類中重寫run()方法

           3.創建MyThread類的對象

           4.啟動線程對象

 

方式二:實現Runnable接口

步驟:1.自定義類MyRunnble實現Runnable接口

           2.MyRunnble里面重寫run()方法

           3.創建MyRunnble類的對象

           4.創建Thread類的對象,並把3步驟的對象作為構造參數傳遞

問題:有了方式一了,為什么還要方式二呢?

          1.可以避免由於Java單繼承帶來的局限性

          2.適合多個相同程序的代碼去處理同一個資源的情況,將線程和程序的代碼、數據分離,充分體現了面向對象的設計思想。

 


(4)線程的調度和優先級問題
    A:線程的調度
      a:分時調度
      b:搶占式調度 (Java采用的是該調度方式)
   B:獲取和設置線程優先級
      a:默認是5
      b:范圍是1-10

 1 /*
 2  * 獲取線程的優先級
 3  *    public final int getPriority():返回線程的優先級
 4  *    
 5  * 設置線程對象的優先級
 6  *    public final void setPriority():設置線程優先級
 7  *    
 8  * 注意:線程默認的優先級是5
 9  *     線程優先級范圍是:1-10
10  *     優先級高僅表示線程獲取CPU時間片的幾率高,這要求次數比較多或者多次運行才能看到好的效果
11  *     
12  */
13 public class ThreadPriorityDemo {
14 
15     public static void main(String[] args) {
16         ThreadPriority tp1 = new ThreadPriority();
17         ThreadPriority tp2 = new ThreadPriority();
18         ThreadPriority tp3 = new ThreadPriority();
19         
20         tp1.setName("aa");
21         tp1.setName("bb");
22         tp1.setName("cc");
23         
24         //獲取默認優先級
25         System.out.println(tp1.getPriority());
26         System.out.println(tp2.getPriority());
27         System.out.println(tp3.getPriority());
28         
29         //設置優先級
30         tp1.setPriority(10);
31         tp2.setPriority(1);
32         
33         tp1.start();
34         tp2.start();
35         tp3.start();
36     }
37 }
 1 public class ThreadPriority extends Thread{
 2     @Override
 3     public void run() {
 4         for(int x = 0; x < 100; x++){
 5             System.out.println(getName() + ":" + x);
 6         }
 7         
 8     }
 9 
10 }

 


(5)線程的控制(常見方法)
A:休眠線程

 1 //線程睡眠  public static void sleep(long millis)
 2 public class ThreadSleepDemo {
 3  
 4     public static void main(String[] args) {
 5         ThreadSleep ts1 = new ThreadSleep();
 6         ThreadSleep ts2 = new ThreadSleep();
 7         ThreadSleep ts3 = new ThreadSleep();
 8         
 9         //設置線程名稱
10         ts1.setName("a");
11         ts2.setName("b");
12         ts3.setName("c");
13         
14         //啟動線程
15         ts1.start();
16         ts2.start();
17         ts3.start();
18         
19     }
20     
21 }
 1 public class ThreadSleep extends Thread {
 2 
 3     @Override
 4     public void run() {
 5         for(int x = 0; x < 100; x++){
 6             System.out.println(getName() + ":" + x + ",時間:" + new Date());
 7             //睡眠
 8             try {
 9                 Thread.sleep(1000);//睡眠
10             } catch (InterruptedException e) {
11                 e.printStackTrace();
12             }
13             
14         }
15         
16     }
17 }

 


B:加入線程

 1 //public final void join():等待線程終止
 2 public class ThreadJoinDemo {
 3 
 4     public static void main(String[] args) {
 5         ThreadJoin tj1 = new ThreadJoin();
 6         ThreadJoin tj2 = new ThreadJoin();
 7         ThreadJoin tj3 = new ThreadJoin();
 8         
 9         tj1.setName("aa");
10         tj2.setName("bb");
11         tj3.setName("cc");
12         
13         tj1.start(); 
14         //等待tj1執行完
15         try {
16             tj1.join();
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20         
21         tj2.start();
22         tj3.start();
23         
24     }
25 }
1 public class ThreadJoin extends Thread {
2     @Override
3     public void run() {
4         for(int x = 0; x < 100; x++){
5             System.out.println(getName() + ":" + x);
6 
7         }
8     }
9 }

 


C:禮讓線程

 1 /*
 2  * public static void yield():暫停當前正在執行的線程,並去執行其他線程
 3  * 可以讓多個線程更加和諧,但是還不能保證完全均勻
 4  */
 5 public class ThreadYieldDemo {
 6     public static void main(String[] args) {
 7         ThreadYield ty1 = new ThreadYield();
 8         ThreadYield ty2 = new ThreadYield();
 9         
10         ty1.setName("a");
11         ty2.setName("b");
12         
13         ty1.start();
14         ty2.start();
15     }
16 
17 }
 1 public class ThreadYield extends Thread{
 2     @Override
 3     public void run() {
 4         for(int x = 0; x < 100; x++){
 5             System.out.println(getName() + ":" + x);
 6 
 7             Thread.yield();
 8         }
 9     }
10 
11 }

 


D:后台線程(守護線程)

 1 /*
 2  * public final void setDaemon(boolean on)
 3  * 將該線程標記為守護線程或者用戶線程
 4  * 當正在運行的線程都是守護線程時,java虛擬機退出
 5  * 該方法必須在啟動前調用
 6  */
 7 public class ThreadDaemonDemo {
 8 
 9     public static void main(String[] args) {
10         ThreadDaemon td1 = new ThreadDaemon();
11         ThreadDaemon td2 = new ThreadDaemon();
12         
13         td1.setName("aa");
14         td2.setName("bb");
15         
16         //設置守護線程
17         td1.setDaemon(true);
18         td2.setDaemon(true);
19         
20         td1.start();
21         td2.start();
22         
23         //設置主類線程名稱
24         Thread.currentThread().setName("dd");
25         for(int x = 0; x < 5; x++){
26             System.out.println(Thread.currentThread().getName() + ":" + x);
27         }
28     }
29 }
1 public class ThreadDaemon extends Thread{
2     @Override
3     public void run() {
4         for(int x = 0; x < 100; x++){
5             System.out.println(getName() + ":" + x);
6         }
7     }
8 
9 }

 


E:終止線程(掌握)

 1 public class ThreadStopDemo {
 2 
 3     public static void main(String[] args) {
 4         ThreadStop ts = new ThreadStop();
 5         
 6         ts.start();
 7         
 8         try {
 9             Thread.sleep(3000);
10             ts.interrupt();
11         } catch (InterruptedException e) {
12             
13             e.printStackTrace();
14         }
15     }
16 }
 1 public class ThreadStop extends Thread{
 2     @Override
 3     public void run() {
 4         System.out.println("開始時間:" + new Date());
 5         try {
 6             Thread.sleep(10000);
 7         } catch (InterruptedException e) {
 8             
 9             //e.printStackTrace();
10             System.out.println("線程被中斷");
11         }
12         
13         System.out.println("結束時間:" + new Date());
14     }
15 
16 }

 


(6)線程的生命周期
A:新建
B:就緒
C:運行
D:阻塞
E:死亡


(7)電影院賣票程序的實現
A:繼承Thread類

 1 //繼承Thread類來實現
 2 public class SellTicketDemo {
 3 
 4     public static void main(String[] args) {
 5         //三個窗口  創建三個線程對象
 6         SellTicket st1 = new SellTicket();
 7         SellTicket st2 = new SellTicket();
 8         SellTicket st3 = new SellTicket();
 9         
10         //設置線程對象名稱
11         st1.setName("窗口1");
12         st2.setName("窗口2");
13         st3.setName("窗口3");
14         
15         //啟動線程
16         st1.start();
17         st2.start();
18         st3.start();
19     }
20 }

 

 1 public class SellTicket extends Thread {
 2 
 3     //定義100張票
 4     //private int tickets = 100;
 5     //為了讓多個線程對象共享這100張票,將其用靜態修飾
 6     private static int tickets = 100;
 7     @Override
 8     public void run() {
 9         //定義100張票
10         //這里每個線程進來都會走這里,相當於每個線程賣自己的100張票,將票數定義到外面
11         //int tickets = 100;
12         
13         //表示電影院一直有票
14         while(true){
15             if(tickets > 0){
16                 System.out.println(getName() + "正在出售第:" + (tickets--) + "張票");
17             }
18         }
19     }
20 }

 


B:實現Runnable接口(多線程問題一般采用這種方法)

 1 //實現Runnable接口的方法
 2 public class SellTicketDemo {
 3 
 4     public static void main(String[] args) {
 5         //創建資源對象
 6         SellTicket st = new SellTicket();
 7         
 8         //創建三個窗口(線程對象)
 9         Thread t1 = new Thread(st, "窗口1");
10         Thread t2 = new Thread(st, "窗口2");
11         Thread t3 = new Thread(st, "窗口3");
12         
13         //啟動線程
14         t1.start();
15         t2.start();
16         t3.start();
17     }
18 }
 1 public class SellTicket implements Runnable {
 2     //定義100張票
 3     private int tickets = 100;
 4     
 5     @Override
 6     public void run() {
 7         while(true){
 8             if(tickets > 0){
 9                 System.out.println(Thread.currentThread()
10                         .getName()+ "正在出售第:" + (tickets--) + "張票" );
11             }
12         }
13     }
14 
15 }

 


(8)電影院賣票程序出問題
A:為了更符合真實的場景,加入了休眠100毫秒。
B:加入休眠后賣票問題
a:同票多次賣

   因為CPU的一次操作必須是原子性的
b:負數票

   隨機性和延遲性導致


(9)多線程安全問題的原因

(也是以后判斷一個程序是否有線程安全問題的依據)
A:是否有多線程環境
B:是否有共享數據
C:是否有多條語句操作共享數據

 

(10)同步解決線程安全問題
A:同步代碼塊
synchronized(對象) {
    需要被同步的代碼;
}


    注意:同步可以解決安全問題的根本原因在於這個對象上面

               該對象如同鎖的功能。多個線程必須是同一把鎖。

 1 //實現Runnable接口的方法
 2 public class SellTicketDemo {
 3 
 4     public static void main(String[] args) {
 5         //創建資源對象
 6         SellTicket st = new SellTicket();
 7         
 8         //創建三個窗口(線程對象)
 9         Thread t1 = new Thread(st, "窗口1");
10         Thread t2 = new Thread(st, "窗口2");
11         Thread t3 = new Thread(st, "窗口3");
12         
13         //啟動線程
14         t1.start();
15         t2.start();
16         t3.start();
17     }
18 }

 

 

 1 public class SellTicket implements Runnable {
 2     //定義100張票
 3     private int tickets = 100;
 4     
 5     //創建鎖對象 同一把鎖
 6     private Object obj = new Object();
 7     
 8     @Override
 9     public void run() {
10         while(true){
11             synchronized (obj) {
12             if(tickets > 0){
13                 try {
14                     Thread.sleep(100);
15                 } catch (InterruptedException e) {
16                     e.printStackTrace();
17                 }
18                 System.out.println(Thread.currentThread()
19                         .getName()+ "正在出售第:" + (tickets--) + "張票" );
20              }
21          }
22      }
23    }
24 }

 

     這里的鎖對象可以是任意對象。

 1 //實現Runnable接口的方法
 2 public class SellTicketDemo {
 3 
 4     public static void main(String[] args) {
 5         //創建資源對象
 6         SellTicket st = new SellTicket();
 7         
 8         //創建三個窗口(線程對象)
 9         Thread t1 = new Thread(st, "窗口1");
10         Thread t2 = new Thread(st, "窗口2");
11         Thread t3 = new Thread(st, "窗口3");
12         
13         //啟動線程
14         t1.start();
15         t2.start();
16         t3.start();
17     }
18 }
 1 public class SellTicket implements Runnable {
 2     //定義100張票
 3     private int tickets = 100;
 4     
 5     //創建鎖對象 同一把鎖
 6     //private Object obj = new Object();
 7     //任意對象
 8     private Demo d = new Demo();
 9     @Override
10     public void run() {
11         while(true){
12             synchronized (d) {
13             if(tickets > 0){
14                 try {
15                     Thread.sleep(100);
16                 } catch (InterruptedException e) {
17                     e.printStackTrace();
18                 }
19                 System.out.println(Thread.currentThread()
20                         .getName()+ "正在出售第:" + (tickets--) + "張票" );
21              }
22          }
23      }
24    }
25 }
26 
27 class Demo{
28     
29 }

 

 

B:同步方法
把同步加在方法上。

這里的鎖對象是this

 1 //實現Runnable接口的方法
 2 public class SellTicketDemo {
 3 
 4     public static void main(String[] args) {
 5         //創建資源對象
 6         SellTicket st = new SellTicket();
 7         
 8         //創建三個窗口(線程對象)
 9         Thread t1 = new Thread(st, "窗口1");
10         Thread t2 = new Thread(st, "窗口2");
11         Thread t3 = new Thread(st, "窗口3");
12         
13         //啟動線程
14         t1.start();
15         t2.start();
16         t3.start();
17     }
18 }
 1 public class SellTicket implements Runnable {
 2     //定義100張票
 3     private int tickets = 100;
 4     
 5     //創建鎖對象 同一把鎖
 6     //private Object obj = new Object();
 7     
 8     //任意對象
 9     private Demo d = new Demo();
10     
11     private int x = 0;
12     @Override
13     public void run() {
14         while(true){
15             if(x%2 == 0){
16                 synchronized (this) {
17                     if(tickets > 0){
18                         try {
19                             Thread.sleep(100);
20                         } catch (InterruptedException e) {
21                             e.printStackTrace();
22                         }
23                         System.out.println(Thread.currentThread()
24                                 .getName()+ "正在出售第:" + (tickets--) + "張票" );
25                      }
26                  }
27              
28             }else{
29                 sellTicket();
30             }
31             x++;
32             
33      }
34   }
35 
36   private synchronized void sellTicket() {
37     
38         if(tickets > 0){
39             try {
40                 Thread.sleep(100);
41             } catch (InterruptedException e) {
42                 e.printStackTrace();
43             }
44             System.out.println(Thread.currentThread()
45                     .getName()+ "正在出售第:" + (tickets--) + "張票" );
46          }
47      }
48         
49 }
50 
51 class Demo{
52  }

 


C:靜態同步方法
把同步加在方法上。

這里的鎖對象是當前類的字節碼文件對象(反射再講字節碼文件對象)

 

 1 //實現Runnable接口的方法
 2 public class SellTicketDemo {
 3 
 4     public static void main(String[] args) {
 5         //創建資源對象
 6         SellTicket st = new SellTicket();
 7         
 8         //創建三個窗口(線程對象)
 9         Thread t1 = new Thread(st, "窗口1");
10         Thread t2 = new Thread(st, "窗口2");
11         Thread t3 = new Thread(st, "窗口3");
12         
13         //啟動線程
14         t1.start();
15         t2.start();
16         t3.start();
17     }
18 }

 

 1 public class SellTicket implements Runnable {
 2     //定義100張票
 3     private static int tickets = 100;
 4     
 5     //創建鎖對象 同一把鎖
 6     //private Object obj = new Object();
 7     
 8     //任意對象
 9     private Demo d = new Demo();
10     
11     private int x = 0;
12     @Override
13     public void run() {
14         while(true){
15             if(x%2 == 0){
16                 synchronized (SellTicket.class) {
17                     if(tickets > 0){
18                         try {
19                             Thread.sleep(100);
20                         } catch (InterruptedException e) {
21                             e.printStackTrace();
22                         }
23                         System.out.println(Thread.currentThread()
24                                 .getName()+ "正在出售第:" + (tickets--) + "張票" );
25                      }
26                  }
27              
28             }else{
29                 sellTicket();
30             }
31             x++;
32             
33      }
34   }
35 
36   private static synchronized void sellTicket() {
37     
38         if(tickets > 0){
39             try {
40                 Thread.sleep(100);
41             } catch (InterruptedException e) {
42                 e.printStackTrace();
43             }
44             System.out.println(Thread.currentThread()
45                     .getName()+ "正在出售第:" + (tickets--) + "張票" );
46          }
47      }
48         
49 }
50 
51 class Demo{
52  }

 

 

(11)回顧以前的線程安全的類
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一個線程不安全的集合類變成一個線程安全的集合類
用Collections工具類的方法即可。

 

 1 public class ThreadDemo3 {
 2 public static void main(String[] args) {
 3     //線程安全的類
 4     StringBuffer sb = new StringBuffer();
 5     Vector<String> v = new Vector<String>();
 6     Hashtable<String, String> hsah = new Hashtable<String, String>();
 7     
 8     //Vector是線程安全的時候考慮使用。但即使安全也不會考慮使用
 9     //用下面這種安全的
10     List<String> list1 = new ArrayList<String>();//線程不安全
11     //線程安全
12     List<String> list2 = Collections.synchronizedList(new ArrayList<String>());
13  }
14 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM