什么是線程?
提到“線程”總免不了要和“進程”做比較,而我認為在Java并發編程中混淆的不是“線程”和“進程”的區別,而是“任務(Task)”。進程是表示資源分配的基本單位。而線程則是進程中執行運算的最小單位,即執行處理機調度的基本單位。關于“線程”和“進程”的區別耳熟能詳,說來說去就一句話:通常來講一個程序有一個進程,而一個進程可以有多個線程。
但是“任務”是很容易忽略的一個概念。我們在實際編碼中通常會看到這么一個包叫做xxx.xxx.task,包下是XxxTask等等以Task后綴名結尾的類。而XxxTask類通常都是實現Runnable接口或者Thread類。嚴格來說,“任務”和并發編程沒多大關系,就算是單線程結構化順序編程中,我們也可以定義一個Task類,在類中執行我們想要完成的一系列操作。“任務”我認為是我們人為定義的一個概念,既抽象又具體,抽象在它指由軟件完成的一個活動,它可以是一個線程,也可以是多個線程共同達到某一目的的操作,具體在于它是我們認為指定實實在在的操作,例如:定時獲取天氣任務(定時任務),下線任務……關鍵就在于不要認為一個任務對應的就是一個線程,也許它是多個線程,甚至在這個任務中是一個線程池,這個線程池處理這個我們定義的操作。
我產生“線程”和“任務”的疑惑就是在《Thinking in Java》這本書的“并發”章節中它將線程直接定義為一個任務,在開篇標題就取名為“定義任務”,并且提到定義任務只需實現Runnable接口.而這個任務則是通過調用start來創建一改新的線程來執行.說來說去有點繞,其實也不必糾結于在書中時而提到線程,時而提到人任務.我認為就記住:任務是我們在編程時所賦這段代碼的實際意義,而線程就關注它是否安全,是否需要安全,這就是后面要提到的線程安全問題.在像我一樣產生疑惑時,不用在意它兩者間的關系和提法。
什么是并發?
提到了并發,那又不得不和并行作比較。并發是指在一段時間內同時做多個事情,比如在1點-2點洗碗、洗衣服等。而并行是指在同一時刻做多個事情,比如1點我左手畫圓右手畫方。兩個很重要的區別就是“一段時間”和“同一時刻”.在操作系統中就是:
1) 并發就是在單核處理中同時處理多個任務。(這里的同時指的是邏輯上的同時)
2) 并行就是在多核處理器中同時處理多個任務。(這里的同時指的就是物理上的同時)
初學編程基本上都是單線程結構化編程,或者說是根本就接觸不到線程這個概念,反正程序照著自己實現的邏輯,程序一步一步按照我們的邏輯去實現并且得到希望輸出的結果。但隨著編程能力的提高,以及應用場景的復雜多變,我們不得不要面臨多線程并發編程。而初學多線程并發編程時,常常出現一些預料之外的結果,這就是涉及到“線程安全”問題。
什么線程安全?
這是在多線程并發編程中需要引起足夠重視的問題,如果你的線程不足夠“安全”,程序就可能出現難以預料,以及難以復現的結果?!禞ava并發編程實戰》提到對線程安全不好做一個定義,我的簡單理解就是:線程安全就是指程序按照你的代碼邏輯執行,并始終輸出預定的結果。書中的給的定義:當多個線程訪問某個類時,這個類始終都能表現出正確的行為,那么就稱這個類是線程安全的。具體有關線程安全的問題,例如原子性、可見性等等不在這里做詳細闡述,適當的時候會進行詳細介紹,簡單說一點,想要這個線程安全,得在訪問的時候給它上個鎖,不讓其他線程訪問,當然這種說法不嚴謹,不過可以暫時這么理解。
以上是從基本概念理論出發來大致了解需要知道的一些概念,下面就針對JDK中有關線程的API來對多線程并發編程做一個了解。
1
2
3
4
5
6
|
java.lang.Object - public void notify() //喚醒這個對象監視器正在等待獲取鎖的一個線程 - public void notifyAll() //喚醒這個對象監視器上正在等待獲取鎖的所有線程 - public void wait() //導致當前線程等待另一個線程調用notify()或notifyAll() - public void wait( long timeout) // 導致當前線程等待另一個線程調用notify()或notifyAll(),或者達到timeout時間 - public void wait( long timeout, int nanos) //與上個方法相同,只是將時間控制到了納秒nanos |
我們先用一個經典的例子——生產者消費者問題來說明上面的API是如何使用的。生產者消費者問題指的的是,生產者生產產品到倉庫里,消費者從倉庫中拿,倉庫滿時生產者不能繼續生產,倉庫為空時消費者不能繼續消費。轉化成程序語言也就是生產者是一個線程
1,消費者是線程
2,倉庫是一個隊列,線程1往隊尾中新增,線程2從隊首中移除,隊列滿時線程1不能再新增,隊列空時線程2不能再移除。
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
package com.producerconsumer; import java.util.Queue; /** * 生產者 * Created by yulinfeng on 2017/5/11. */ public class Producer implements Runnable{ private final Queue<String> queue; private final int maxSize; public Producer(Queue<String> queue, int maxSize) { this .queue = queue; this .maxSize = maxSize; } public void run() { produce(); } /** * 生產 */ private void produce() { try { while ( true ) { synchronized (queue) { if (queue.size() == maxSize) { System.out.println( "生產者:倉庫滿了,等待消費者消費" ); queue.wait(); } System.out.println( "生產者:" + queue.add( "product" )); queue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } package com.producerconsumer; import java.util.Queue; /** * 消費者 * Created by yulinfeng on 2017/5/11. */ public class Consumer implements Runnable { private final Queue<String> queue; public Consumer(Queue<String> queue) { this .queue = queue; } public void run() { consume(); } /** * 消費 */ private void consume() { synchronized (queue) { try { while ( true ) { if (queue.isEmpty()) { System.out.println( "消費者:倉庫空了,等待生產者生產" ); queue.wait(); } System.out.println( "消費者:" + queue.remove()); queue.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } package com.producerconsumer; import java.util.LinkedList; import java.util.Queue; /** * Created by yulinfeng on 2017/5/11. */ public class Main { public static void main(String[] args) { Queue<String> queue = new LinkedList<String>(); int maxSize = 100 ; Thread producer = new Thread( new Producer(queue, maxSize)); Thread consumer = new Thread( new Consumer(queue)); producer.start(); consumer.start(); } } |
這個生產者消費者問題的實現,我采用線程不安全的LinkedList,使用內置鎖synchronized來保證線程安全,在這里我們不討論synchronized,主要談notify()、notifyAll()和wait()。
在這里例子中,作為生產者,當隊列滿時調用了隊列的wait()方法,表示等待,并且此時釋放了鎖。作為消費者此時獲取到鎖并且移除隊首元素時調用了notifyAll()方法,此時生產者由wait等待狀態轉換為喚醒狀態,但注意!此時僅僅是線程被喚醒了,有了爭奪CPU資源的資格,并不代表下一步就一定是生產者生產,還有可能消費者繼續爭奪了CPU資源。一定記住是被喚醒了,有資格爭奪CPU資源。notifyAll()表示的是喚醒所有等待的線程,所有等待的線程被喚醒過后,都有了爭奪CPU資源的權利,至于是誰會獲得這個鎖,那不一定。而如果是使用notify(),那就代表喚醒所有等待線程中的一個,只是一個被喚醒具有了爭奪CPU的權力,其他沒被喚醒的線程繼續等待。如果等待線程就只有一個那么notify()和notifyAll()就沒區別,不止一個那區別就大了,一個是只喚醒其中一個,一個是喚醒所有。喚醒不是代表這個線程就一定獲得CPU資源一定獲得鎖,而是有了爭奪的權利。
1
2
3
4
5
|
java.lang.Thread - public void join() - public void sleep() - public static void yield() -…… |
針對Thread線程類,我們只說常見的幾個不容易理解的方法,其余方法不在這里做詳細闡述。
關于sleep()方法,可能很容易拿它和Object的wait方法作比較。兩個方法很重要的一點就是sleep不會釋放鎖,而wait會釋放鎖。在上面的生產者消費者的生產或消費過程中添加一行Thread.sleep(5000),你將會發現執行到此處時,這個跟程序都會暫停執行5秒,不會有任何其他線程執行,因為它不會釋放鎖。
關于join()方法,JDK7的解釋是等待線程結束(Waits for this thread to die)似乎還是不好理解,我們在main函數中啟動兩個線程,在啟動完這兩個線程后main函數再執行其他操作,但如果不加以限制,有可能main函數率先執行完需要的操作,但如果在main函數中加入join方法,則表示阻塞等待這兩個線程執行結束后再執行main函數后的操作,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.join; public class Main { public static void main(String[] args) throws Exception{ Thread t1 = new Thread( new Task( 0 )); Thread t2 = new Thread( new Task( 0 )); t1.start(); t2.start(); t1.join(); t2.join(); System.out.print( "main結束" ); } } |
上面個例子如果沒有join方法,那么“main”結束這條輸出語句可能就會先于t1、t2,加上在啟動線程的調用方使用了線程的join方法,則調用方則會阻塞線程執行結束過后再執行剩余的方法。
關于Thread.yield()方法,本來這個線程處于執行狀態,其他線程也想爭奪這個資源,突然,這個線程不想執行了想和大家一起來重新奪取CPU資源。所以Thread.yield也稱讓步。從下一章開始就正式開始了解java.util.concurrent。
以上這篇基于線程、并發的基本概念(詳解)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持服務器之家。