首頁 > Java > java教程 > 主體

Java多執行緒詳解

PHP中文网
發布: 2018-05-15 10:38:57
原創
2534 人瀏覽過

多執行緒背景知識介紹

利用多執行緒可以簡化模型,寫功能強大的程式碼,但是要寫好多執行緒卻不容易,需要一個長期實踐的過程。 

多執行緒基礎概念介紹

進程與執行緒

①  進程:程式(任務)的執行過程。動態性

持有資源(共享內存,共享檔案)和線程。載體

範例:Eclipse、QQ

②  執行緒:

Eclipse:原始碼文字編輯、原始碼編譯、語法校驗。

QQ:文字聊天、收發檔案。

如果把行程比喻成一個班級,那麼這個班級中的每一個學生就是線程。學生是班級當中的最小單元,構成班級當中的最小單位。一個班級可以有多個學生,這些學生都使用班級當中共同的桌椅、黑板、粉筆。

線程是系統中最小的執行單元,同一個進程中有多個線程,線程共享進程的資源。

執行緒的互動

互斥、同步。

Java線程初步體驗

Java.lang

class Thread

interface Runnable

public void run ()

執行緒的常用方法

## Thread(Runnable target,String name) 執行緒的方法執行緒休眠static void sleep(long millis,int nanos)使其他執行緒等待目前執行緒終止void join(long millis,int nanos)

#方法簽章

簡介

#執行緒的建立

Thread()

 

#Thread(String name)

 

#Thread(Runnable target)

void start()

啟動執行緒

static void sleep(long millis)

void join()

void join( long millis)

#########static void yield()############目前執行緒釋放處理器資源################### 取得執行緒參考####### #####static Thread currentThread()############傳回目前執行的執行緒參考###############

兩個執行緒不做任何處理的時候,會交替運行。

當使用boolean類型控制執行緒的循環時,要在變數前面加上volatile關鍵字,volatile保證了執行緒可以正確的讀取其他執行緒寫入的值。

注意點:

sleep()方法的作用:使執行緒休眠指定的時間。

join()方法的作用: //使其他執行緒等待目前執行緒執行完畢。

多執行緒案例

範例1:

1 package com.czgo;  
2   
3    
4   
5 /**  
6   
7  * 线程先生  
8   
9  * @author 疯子 
10  
11  * 
12  
13  */ 
14  
15 public class Actor extends Thread { 
16  
17     @Override 
18  
19     public void run() { 
20  
21         //getName():获取当前线程的名称 
22  
23         System.out.println(getName()+"是一个演员!"); 
24  
25         //用来记录线程跑的次数 
26  
27         int count = 0; 
28  
29         boolean keepRunning = true; 
30  
31         while(keepRunning){ 
32  
33             System.out.println(getName()+"登台演出"+(++count)); 
34  
35             if(count==100){ 
36  
37                 keepRunning = false; 
38  
39             } 
40  
41             if(count%10==0){ 
42  
43                 try { 
44  
45                     Thread.sleep(1000); 
46  
47                 } catch (InterruptedException e) { 
48  
49                     e.printStackTrace(); 
50  
51                 } 
52  
53             } 
54  
55         } 
56  
57         System.out.println(getName()+"的演出结束了!"); 
58  
59     } 
60  
61     
62  
63     public static void main(String[] args) { 
64  
65         Thread actor = new Actor(); 
66  
67         //setName:设置线程的名称 
68  
69         actor.setName("Mr.Thread"); 
70  
71         //启动线程 
72  
73         actor.start(); 
74  
75         
76  
77         Thread actressThread = new Thread(new Actress(),"Ms.Runnable"); 
78  
79         actressThread.start(); 
80  
81     } 
82  
83 } 
84  
85   
86  
87 class Actress implements Runnable{ 
88  
89     @Override 
90  
91     public void run() { 
92  
93         //getName():获取当前线程的名称 
94  
95         //currentThread()获取当前线程的引用 
96  
97         System.out.println(Thread.currentThread().getName()+"是一个演员!"); 
98  
99         //用来记录线程跑的次数
100 
101         int count = 0;
102 
103         boolean keepRunning = true;
104 
105         while(keepRunning){
106 
107             System.out.println(Thread.currentThread().getName()+"登台演出"+(++count));
108 
109             if(count==100){
110 
111                 keepRunning = false;
112 
113             }
114 
115             if(count%10==0){
116 
117                 try {
118 
119                     Thread.sleep(1000);
120 
121                 } catch (InterruptedException e) {
122 
123                     e.printStackTrace();
124 
125                 }
126 
127             }
128 
129         }
130 
131         System.out.println(Thread.currentThread().getName()+"的演出结束了!");   
132 
133     }134 135 }
登入後複製

範例2:

軍隊:

 1 package com.czgo; 
 2  
 3   
 4  
 5 /** 
 6  
 7  * 军队线程 
 8  
 9  * 模拟作战双方的行为
 10 
 11  * @author 疯子
 12 
 13  *
 14 
 15  */
 16 
 17 public class ArmyRunnable implements Runnable {
 18 
 19    
 20 
 21     //volatile保证了线程可以正确的读取其他线程写入的值
 22 
 23     //可见性 ref JMM,happens-before
 24 
 25     volatile boolean keepRunning = true;
 26 
 27    
 28 
 29     @Override
 30 
 31     public void run() {
 32 
 33        
 34 
 35         while(keepRunning){
 36 
 37             //发动5连击
 38 
 39             for(int i=0;i<5;i++){
 40 
 41                 System.out.println(Thread.currentThread().getName()+"进攻对方["+i+"]");
 42 
 43                 //让出了处理器时间,下次谁进攻还不一定呢!
 44 
 45                 Thread.yield();
 46 
 47             }
 48 
 49         }
 50 
 51        
 52 
 53         System.out.println(Thread.currentThread().getName()+"结束了战斗!");
 54 
 55        
 56 
 57     }
 58 
 59 }
登入後複製

關鍵人物:

1 package com.czgo; 
2  
3   
4  
5 /** 
6  
7  * 关键人物 
8  
9  * @author 疯子
10 
11  *
12 
13  */
14 
15 public class KeyPersonThread extends Thread {
16 
17     @Override
18 
19     public void run() {
20 
21         System.out.println(Thread.currentThread().getName()+"开始了战斗!"); 
22 
23         for(int i=0;i<10;i++){
24 
25             System.out.println(Thread.currentThread().getName()+"左突右杀,攻击随军...");
26 
27         }
28 
29         System.out.println(Thread.currentThread().getName()+"结束了战斗!");
30 
31  
32 
33     }
34 
35 }
登入後複製

舞台:

  1 package com.czgo;  
  2   
  3    
  4   
  5 /**  
  6   
  7  * 隋唐演义大戏舞台  
  8   
  9  * @author win7 
  10  
  11  * 
  12  
  13  */ 
  14  
  15 public class Stage extends Thread { 
  16  
  17     
  18  
  19     @Override 
  20  
  21     public void run() { 
  22  
  23         System.out.println("欢迎观看隋唐演义"); 
  24  
  25         
  26  
  27         try { 
  28  
  29             Thread.sleep(5000); 
  30  
  31         } catch (InterruptedException e2) { 
  32  
  33             e2.printStackTrace(); 
  34  
  35         } 
  36  
  37         
  38  
  39         System.out.println("大幕徐徐拉开"); 
  40  
  41         
  42  
  43         try { 
  44  
  45             Thread.sleep(5000); 
  46  
  47         } catch (InterruptedException e2) { 
  48  
  49             e2.printStackTrace(); 
  50  
  51         } 
  52 
  53         
  54  
  55         System.out.println("话说隋朝末年,隋军与农民起义军杀得昏天暗地..."); 
  56  
  57         
  58  
  59         //隋朝军队 
  60  
  61         ArmyRunnable armyTaskOfSuiDynasty = new ArmyRunnable(); 
  62 
  63         //农民起义军 
  64  
  65         ArmyRunnable armyTaskOfRevolt = new ArmyRunnable(); 
  66  
  67         
  68  
  69         //使用Runnable接口创建线程 
  70  
  71         Thread armyOfSuiDynasty = new Thread(armyTaskOfSuiDynasty,"隋军"); 
  72  
  73         Thread armyOfSuiRevolt = new Thread(armyTaskOfRevolt,"农民起义军"); 
  74  
  75         
  76  
  77         //启动线程,让军队开始作战 
  78  
  79         armyOfSuiDynasty.start(); 
  80  
  81         armyOfSuiRevolt.start(); 
  82  
  83         
  84  
  85         //舞台线程休眠,大家专心观看军队的厮杀 
  86  
  87         try { 
  88  
  89             //Thread会指向当前类的线程 
  90 
  91             Thread.sleep(50); 
  92  
  93         } catch (InterruptedException e) { 
  94  
  95             e.printStackTrace(); 
  96  
  97         } 
  98
  99         System.out.println("正当双方激战正酣,半路杀出了个程咬金");
  100 
  101        
  102 
  103         Thread mrCheng = new KeyPersonThread();
  104 
  105         mrCheng.setName("程咬金");
  106 
  107        
  108 
  109         System.out.println("程咬金的理想就是结束战争,使百姓安居乐业!");
  110 
  111        
  112 
  113         //停止军队作战
  114 
  115         //停止线程的方法
  116 
  117         armyTaskOfSuiDynasty.keepRunning=false;
  118 
  119         armyTaskOfRevolt.keepRunning=false;
  120 
  121       
  122 
  123         try {
  124 
  125             Thread.sleep(2000);
  126 
  127         } catch (InterruptedException e1) {
  128 
  129             e1.printStackTrace();
  130 
  131         }
  132 
  133        134 135         //历史大戏留给关键人物
  136 
  137         mrCheng.start();
  138 
  139        
  140 
  141         try {
  142 
  143             //使其他线程等待当前线程执行完毕
  144 
  145             mrCheng.join();
  146 
  147         } catch (InterruptedException e) {
  148 
  149             e.printStackTrace();150 151         }
  152 
  153        
  154 
  155         System.out.println("战争结束,人民安居乐业,程先生实现了积极的人生梦想,为人民作出了贡献!");
  156 
  157         System.out.println("谢谢观看隋唐演义,再见!");
  158 
  159     }
  160 
  161    
  162 
  163     public static void main(String[] args) {
  164 
  165         new Stage().start();
  166 
  167     }
  168 
  169 }
登入後複製

執行緒的正確停止

##如何正確的停止Java中的執行緒:

可以透過boolean類型來控制循環的退出。

not stop方法

stop()方法會讓執行緒戛然而止。

stop()方法停止執行緒是錯誤的方法。

線程的互動

爭用條件Race Condition

當多個執行緒同時共享存取相同資料(記憶體區域)時,每個執行緒都嘗試操作該數據,從而導致數據被破壞(corrupted),這種現象稱為爭用條件。

什麼是互斥?

互斥是怎麼實現的?

只能被一個執行緒所訪問,互斥。

互斥的實作:synchronized(intrinsic lock)加鎖。

同步的實作:wait()/notify()/notifyAll()。

如何擴展Java並發的知識

Java Memory Mode

         JMM描述了Java執行緒如何透過記憶體互動

    -before

         Synchronized,volatile&final

Locks&Condition

        ##線程安全性

         原子性與可見性

         Java.util.concurrent.atomic

#o 

##多線程編程常用的交互模型

         Producer-Consumer模型

         Read-Write Lock模型

         Future模型

         Worker Thread模型

Java5中並發程式設計工具

         Java.util.concurrent

         執行緒池ExecutorService

#推薦兩本書:

Core Java

Java concurrency in practice

#執行緒所建立的兩種方式比較

Thread:

#①  繼承Thread類別;

Runnable:

①  Runnable方式可避免Thread方式因Java單一繼承特性所帶來的缺陷。

②  Runnable的程式碼可以被多個執行緒(Thread實例)共享,適合於多個執行緒處理相同資源的情況。

案例:

Thread:

 1 package com.czgo; 
 2  
 3   
 4  
 5 class MyThread extends Thread{ 
 6  
 7     
 8  
 9     private int ticketsCont = 5;    //一共有5张火车票
 10 
 11    
 12 
 13     private String name;            //窗口,也即是线程的名字
 14 
 15    
 16 
 17     public MyThread(String name){
 18 
 19         this.name = name;
 20 
 21     }
 22 
 23    24 25     @Override
 26 
 27     public void run() {
 28 
 29        30 31         while(ticketsCont>0){
 32 
 33             ticketsCont--;      //如果还有票,就卖掉一张
 34 
 35             System.out.println(name+"卖了1张票,剩余票数为:"+ticketsCont);
 36 
 37         }
 38 
 39     }
 40 
 41    
 42 
 43 }
 44 
 45  
 46 
 47 public class TicketsThread {
 48 
 49  
 50 
 51     public static void main(String[] args) {
 52 
 53         //创建3个线程,模拟三个窗口卖票
 54 
 55         MyThread mt1 = new MyThread("窗口1");
 56 
 57         MyThread mt2 = new MyThread("窗口2");
 58 
 59         MyThread mt3 = new MyThread("窗口3");
 60 
 61        62 63         //启动这三个线程,也即是窗口,开始卖票
 64 
 65         mt1.start();
 66 
 67         mt2.start();
 68 
 69         mt3.start();
 70 
 71        
 72 
 73     }
 74 
 75  
 76 
 77 }
登入後複製

Runnable:

 1 package com.czgo; 
 2  
 3   
 4  
 5 class MyThread implements Runnable{ 
 6  
 7     
 8  
 9     private int ticketsCont = 5;    //一共有5张火车票
 10 
 11  
 12 
 13     @Override
 14 
 15     public void run() {
 16 
 17        
 18 
 19         while(ticketsCont>0){
 20 
 21             ticketsCont--;      //如果还有票,就卖掉一张
 22 
 23             System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余票数为:"+ticketsCont);
 24 
 25         }
 26 
 27        
 28 
 29     }
 30 
 31    
 32 
 33 }
 34 
 35  
 36 
 37 public class TicketsRunnable {
 38 
 39  
 40 
 41     public static void main(String[] args) {
 42 
 43        
 44 
 45         MyThread mt1 = new MyThread();
 46 
 47         MyThread mt2 = new MyThread();
 48 
 49         MyThread mt3 = new MyThread();
 50 
 51        
 52 
 53         //创建三个线程来模拟三个售票窗口
 54 
 55         Thread th1 = new Thread(mt1,"窗口1");
 56 
 57         Thread th2 = new Thread(mt2,"窗口2");
 58 
 59         Thread th3 = new Thread(mt3,"窗口3");
 60 
 61        
 62 
 63         //启动这三个线程,也即是三个窗口开始卖票
 64 
 65         th1.start();
 66 
 67         th2.start();
 68 
 69         th3.start();
 70 
 71  
 72 
 73     }
 74 
 75  
 76 
 77 }
登入後複製

執行緒的生命週期

##圖示:

 

建立:新建一個執行緒對象,如Thread thd = new Thread()。

就緒:創建了線程物件後,呼叫了線程的start()方法(注意:此時線程只是進入了線程隊列,等待獲取cpu服務,具備了運行的條件,但不一定已經開始運行了)。

運行:處於就緒狀態的線程,一旦取得了CPU資源,便進入到運行狀態,開始執行run()方法裡面的邏輯。

終止:執行緒的run()方法執行完畢,或是執行緒呼叫了stop()方法,執行緒便進入終止狀態。

阻塞:正在執行的線程在某些情況下,由於某種原因而暫時讓出了CPU資源,暫停了自己的執行,便進入了阻塞狀態,如調用了sleep()方法。

 

線程的守護神-守護線程

#Java線程有兩類:

用戶執行緒:運行在前台,執行具體的任務。

例如程式的主執行緒、連接網路的子執行緒等都是使用者執行緒。

 

守護執行緒:運行在後台,為其他前台執行緒服務。 特點:一旦所有使用者執行緒都結束運行,守護執行緒就會隨JVM一起結束工作。

應用程式:資料庫連線池中的監控執行緒、JVM虛擬機啟動後的監控執行緒。 最常見的守護線程:垃圾回收線程。

如何设置守护线程

可以通过调用Thread类的setDaemon(true)方法来设置当前线程为守护线程。

注意事项:

setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常。

在守护线程中产生的新线程也是守护线程

不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。

案例:

  1 package com.czgo;  
  2   
  3    
  4   
  5 import java.io.File;  
  6   
  7 import java.io.FileOutputStream;  
  8   
  9 import java.io.IOException; 
  10  
  11 import java.io.OutputStream; 
  12  
  13 import java.util.Scanner; 
  14  
  15   
  16  
  17 class DaemonThread implements Runnable{ 
  18  
  19     @Override 
  20  
  21     public void run() { 
  22  
  23         System.out.println("进入守护线程"+Thread.currentThread().getName()); 
  24  
  25         try { 
  26  
  27             writeToFile(); 
  28  
  29         } catch (IOException e) { 
  30  
  31             e.printStackTrace(); 
  32  
  33         } catch (InterruptedException e) { 34  35             e.printStackTrace(); 
  36  
  37         } 
  38  
  39         System.out.println("退出守护线程"+Thread.currentThread().getName()); 
  40  
  41         
  42  
  43     } 
  44  
  45     
  46  
  47     private void writeToFile() throws IOException, InterruptedException{ 
  48  
  49         File filename = new File("C:\\ide"+File.separator+"daemon.txt"); 
  50  
  51         OutputStream os = new FileOutputStream(filename,true); 
  52  
  53         int count = 0; 
  54  
  55         while(count<999){ 
  56  
  57             os.write(("\r\nword"+count).getBytes()); 
  58  
  59             System.out.println("守护线程"+Thread.currentThread().getName()+"向文件中写入了word"+count++); 
  60 
  61             Thread.sleep(1000); 
  62  
  63         } 
  64  
  65         os.close(); 
  66  
  67     } 
  68  
  69 } 
  70  
  71   
  72  
  73 public class DaemonThreadDemo { 
  74  
  75   
  76  
  77     public static void main(String[] args) { 
  78  
  79         
  80  
  81         System.out.println("进入主线程"+Thread.currentThread().getName()); 
  82  
  83         DaemonThread daemonThread = new DaemonThread(); 
  84  
  85         Thread thread = new Thread(daemonThread); 
  86  
  87         thread.setDaemon(true); 
  88  
  89         thread.start(); 
  90  
  91         
  92  
  93         Scanner sc = new Scanner(System.in); 
  94  
  95         sc.next();
  96  
  97         
  98  
  99         System.out.println("退出主线程"+Thread.currentThread().getName());
  100 
  101     }
  102 
  103  
  104 
  105 }
登入後複製


使用jstack生成线程快照

jstack

作用:生成jvm当前时刻线程的快照(threaddump,即当前进程中所有线程的信息)

目的:帮助定位程序问题出现的原因,如长时间停顿、cpu占用率过高等。

如何使用jstack

jstack –l pid

 

内存可见性

可见性介绍

可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。

共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

Java内存模型(JMM)

Java内存模型(Java Memory Model)描述了程序中各种变量(线程共享变量)的访问规则,以及在Java中将变量存储到内存和从内存中读取出变量这样的底层细节。

所有变量都存储在主内存中

每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。

 

线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写。

不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

共享变量可见性实现的原理

线程1对共享变量的修改要想被线程2及时看到,必须要经过如下2个步骤:

把工作内存1中更新过的共享变量刷新到主内存中。

将主内存中最新的共享变量的值更新到工作内存2中。

可见性

要实现共享变量的可见性,必须保证两点:

线程修改后的共享变量值能够及时从工作内存刷新到主内存中。

其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中。

可见性的实现方式

Java语言层面支持的可见性实现方式:

Synchronized

Volatile

Synchronized实现可见性

Synchronized能够实现:

原子性(同步);

可见性

JMM关于Synchronized的两条规定:

线程解锁前,必须把共享变量的最新值刷新到主内存中;

线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)

线程解锁前对共享变量得修改在下次加锁时对其他线程可见。

线程执行互斥代码的过程:

  1. 获得互斥锁

  2. 清空工作内存

  3. 从主内存中拷贝变量的最新副本到工作内存

  4. 执行代码。

  5. 将更改后的共享变量的值刷新到主内存。

  6. 释放互斥锁。

重排序

重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化。

  1. 编译器优化的重排序(编译器优化)。

  2. 指令级并行重排序(处理器优化)。

  3. 内存系统的重排序(处理器优化)。

as-if-serial

as-if-serial:无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)。

例子:

Int num = 1;

Int num2 = 2;

Int sum = num+num2;

单线程:第1、2行的顺序可以重排,但第3行不能

重排序不会给单线程带来内存可见性问题

多线程中程序交错执行时,重排序可能会造成内存可见性问题。

Volatile实现可见性

Volatile关键字:

能够保证volatile变量的可见性

不能保证volatile变量复合操作的原子性

Volatile如何实现内存可见性:

深入來說:透過加入記憶體屏障和禁止重排序優化來實現的。

對volatile變數執行寫入作業時,會在寫入作業後加入一個store屏障指令

#對volatile變數執行讀取操作時,會在讀取操作前加入load屏障指令

通俗的講:volatile變數在每次被執行緒存取時,都強迫從主記憶體重讀該變數的值,而當該變數發生變化時,又會強迫執行緒將最新的值刷新到主記憶體。這樣任何時刻,不同執行緒總是能看到該變數的最新值。

線程寫volatile變數的過程:

  1. 改變線程工作記憶體中volatile變數副本的值

  2. ##將改變後的副本的值從工作記憶體刷新到主記憶體

線程讀取volatile變數的過程:

  1. 從主記憶體讀取volatile變量的最新值到線程的工作記憶體中

  2. 從工作記憶體讀取volatile變數的副本。

Volatile適用場合

要在多執行緒中安全的使用volatile變量,必須同時滿足:

1. 對變數的寫入操作不依賴其目前值

2.  布爾,用來記錄溫度

3.該變數沒有包含在具有其他變數的不變式中。

結語:不要在意別人在背後怎麼看你說你,因為這些言語改變不了事實,卻可能會攪亂你的心。

以上是Java多執行緒詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板