java多線程總結二
之前也總結了一篇文章《java多線程總結》,地址:http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html
這個就叫做第二篇吧,呵呵。
線程一般有6個狀態:
新建狀態:NEW
可運行狀態:RUNNABLE
休眠狀態:TIMED_WAITING
等待狀態:WAITING
阻塞狀態:BLOCKED
終止狀態“TERMINATED
當我們使用new創建線程之後,線程處於新建狀態,當調用start方法之後,線程出於可運行狀態,當線程需要獲得對象的內置鎖,而這個鎖被其他線程所佔用的時候,線程就出於阻塞狀態,當線程等待其他線程通知調度表可以運行時,線程處於等待狀態,當一個含有時間參數的方法,必須sleep()方法,可以讓線程處於計時等待狀態,當run()方法運行完畢或者出現異常,線程處於終止狀態。
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
|
package Thread; public
class ThreadStateDemo{ public
static void main(String[] args)
throws Exception{ ThreadState state = new
ThreadState(); Thread demo = new
Thread(state); System.out.println( "新建狀態:"
+ demo.getState()); demo.start(); System.out.println( "可運行狀態:"
+ demo.getState()); Thread.sleep( 100 ); System.out.println( "休眠狀態:"
+ demo.getState()); Thread.sleep( 1000 ); System.out.println( "等待狀態:"
+ demo.getState()); state.notifyWait(); System.out.println( "阻塞狀態:"
+ demo.getState()); Thread.sleep( 1000 ); System.out.println( "終止狀態“"
+ demo.getState()); } } class ThreadState
implements Runnable{ @Override public
void run(){ try { waitForASecond(); waitForAYear(); } catch (Exception e){ e.printStackTrace(); } } // 當前線程等待1秒 public
synchronized void
waitForASecond() throws
Exception{ wait( 1000 ); } // 當前線程一直等待 public
synchronized void
waitForAYear() throws
Exception{ wait(); } // 喚醒線程 public
synchronized void
notifyWait() throws
Exception{ notify(); } } |
【運行結果】:
新建狀態:NEW
可運行狀態:RUNNABLE
休眠狀態:TIMED_WAITING
等待狀態:WAITING
阻塞狀態:BLOCKED
終止狀態“TERMINATED
線程組表示一個線程線程的集合,線程組中也可以包含其他的線程組。線程組構成一棵樹。
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
|
package Thread; import java.util.ArrayList; import java.util.List; public
class ThreadGroupDemo{ public
static void main(String[] args){ for (String str : getThreadGroups(GetRootThreadGroups())){ System.out.println(str); } } // 獲得根線程組 private
static ThreadGroup GetRootThreadGroups(){ // 獲得當前的線程組 ThreadGroup rootGroup = Thread.currentThread().getThreadGroup(); while ( true ){ if (rootGroup.getParent() !=
null ){ rootGroup = rootGroup.getParent(); } else { break ; } } return
rootGroup; } // 獲得給定線程組中所有線程名 public
static List<String> getThreads(ThreadGroup group){ List<String> threadList = new
ArrayList<String>(); Thread[] threads = new
Thread[group.activeCount()]; int
count = group.enumerate(threads, false ); for ( int
i = 0 ; i < count; i++){ threadList.add(group.getName() + " 線程組 "
+ threads[i].getName()); } return
threadList; } // 獲得線程組中所有子線程組 public
static List<String> getThreadGroups(ThreadGroup group){ List<String> threadList = getThreads(group); ThreadGroup[] groups = new
ThreadGroup[group.activeGroupCount()]; int
count = group.enumerate(groups, false ); for ( int
i = 0 ; i < count; i++){ threadList.addAll(getThreads(groups[i])); } return
threadList; } } |
【運行結果】:
system 線程組 Reference Handler
system 線程組 Finalizer
system 線程組 Signal Dispatcher
system 線程組 Attach Listener
main 線程組 main
使用守護線程
java中的線程分爲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
|
package Thread; public
class DaemonThreadTest{ public
static void main(String[] args){ Thread worker = new
Thread( new Worker()); Thread timer = new
Thread( new Timer()); //設置守護線程 timer.setDaemon( true ); worker.start(); timer.start(); } } class Worker
implements Runnable{ @Override public
void run(){ for ( int
i = 0 ; i <
5 ; ++i){ System.out.println( "rollen真帥! 第"
+ i + "次" ); } } } class Timer implements
Runnable{ @Override public
void run(){ long
currentTime = System.currentTimeMillis(); long
processTime = 0 ; while ( true ){ if ((System.currentTimeMillis() - currentTime) > processTime){ processTime = System.currentTimeMillis() - currentTime; System.out.println( "程序運行時間:"
+ processTime); } } } } |
rollen真帥! 第0次
程序運行時間:1
程序運行時間:2
程序運行時間:3
程序運行時間:4
程序運行時間:5
rollen真帥! 第1次
rollen真帥! 第2次
rollen真帥! 第3次
rollen真帥! 第4次
程序運行時間:6
終止指定的線程
雖然在Thread類中提供了stop()方法可以終止線程,但是由於其固有的不安全性,所以一般不要採用,本例子只是起到拋磚引玉的作用。
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
|
package Thread; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public
class ThreadStopDemo extends
JFrame{ public
ThreadStopDemo(){ panel.setLayout( new
FlowLayout(FlowLayout.CENTER)); panel.add(label); panel.add(startButton); panel.add(endButton); setContentPane(panel); setSize( 200 ,
300 ); setVisible( true ); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); startButton.addActionListener( new
ActionListener(){ @Override public
void actionPerformed(ActionEvent e){ counter = new
CounterThread(); new
Thread(counter).start(); } }); endButton.addActionListener( new
ActionListener(){ @Override public
void actionPerformed(ActionEvent e){ if (counter ==
null ){ return ; } counter.setStop( false ); } }); } public
static void main(String[] args){ new
ThreadStopDemo(); } class
CounterThread implements
Runnable{ @Override public
void run(){ while ( this .flag){ try { Thread.sleep( 500 ); } catch (Exception e){ e.printStackTrace(); } label.setText( "更新"
+ (count++) + "更新" ); } } public
void setStop( boolean
flag){ this .flag = flag; } private
int count = 0 ; private
boolean flag = true ; } private
CounterThread counter = null ; private
final JPanel panel = new
JPanel(); private
final JLabel label = new
JLabel( "更新0次" ); private
final JButton startButton = new
JButton( "開始" ); private
final JButton endButton = new
JButton( "結束" ); } |
【運行結果】:
線程的插隊
在編寫多線程的程序的時候,經常會遇到讓一個線程優先於另外i個線程運行的情況,此時,除了設置這個線程的優先級高(不推薦這種方法)之外,更加直接的辦法是採用Thread類中的join()方法。當插隊的線程運行結束之後,其他的線程才能運行。
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
|
package Thread; public
class ThreadJoinDemo{ public
static void main(String[] args){ Thread demo1 = new
Thread( new EmergencyThread()); demo1.start(); for ( int
i = 0 ; i <
5 ; ++i){ try { Thread.sleep( 100 ); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println( "正常情況 :"
+ i + "號車開始出發" ); try { //開始插隊 demo1.join(); } catch (InterruptedException e){ e.printStackTrace(); } } } } class EmergencyThread
implements Runnable{ @Override public
void run(){ for ( int
i = 0 ; i <
5 ; ++i){ try { Thread.sleep( 100 ); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println( "緊急情況 :"
+ i + "號車開始出發" ); } } } |
【運行結果】:
正常情況 :0號車開始出發
緊急情況 :0號車開始出發
緊急情況 :1號車開始出發
緊急情況 :2號車開始出發
緊急情況 :3號車開始出發
緊急情況 :4號車開始出發
正常情況 :1號車開始出發
正常情況 :2號車開始出發
正常情況 :3號車開始出發
正常情況 :4號車開始出發
如果我們去掉join哪一行的話,運行結果:(結果不唯一)
緊急情況 :0號車開始出發
正常情況 :0號車開始出發
緊急情況 :1號車開始出發
正常情況 :1號車開始出發
正常情況 :2號車開始出發
緊急情況 :2號車開始出發
緊急情況 :3號車開始出發
正常情況 :3號車開始出發
正常情況 :4號車開始出發
緊急情況 :4號車開始出發
線程的同步
多線程編程的一個重要原因是實現數據的共享,但是如果兩個線程同時修改一個數據的話,則會產生同步問題。
下面採用一個2個人同時往銀行存錢的例子,銀行卡初始金額爲100元。每次存10元,大家仔細查看餘額。(對於簡單的多線程,出錯的概率很小,今天很不巧,我一向地下的RP今天居然爆發了,實驗了很多次,都沒錯,最後終於出現了)
先看一下結果吧:
案例說兩側不能出現一樣餘額的。
代碼如下:
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
|
package Thread; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; public
class UnSynBank extends
JFrame{ public
UnSynBank(){ panel.setLayout( new
GridLayout( 2 ,
2 , 3 ,
3 )); panel.add(label1); panel.add(label2); JScrollPane js1 = new
JScrollPane(oneArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js1); JScrollPane js2 = new
JScrollPane(twoArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js2); panel2.add(panel); panel2.add(statrButton); setContentPane(panel2); statrButton.addActionListener( new
ActionListener(){ @Override public
void actionPerformed(ActionEvent e){ Thread demo1 = new
Thread( new Transfer(bank, oneArea)); demo1.start(); Thread demo2 = new
Thread( new Transfer(bank, twoArea)); demo2.start(); } }); setSize( 300 ,
400 ); setVisible( true ); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public
static void main(String[] args){ new
UnSynBank(); } private
final Bank bank = new
Bank(); JPanel panel = new
JPanel(); JPanel panel2 = new
JPanel(); private
final JButton statrButton = new
JButton( "開始存錢" ); private
final JLabel label1 = new
JLabel( "一號線程" ); private
final JLabel label2 = new
JLabel( "二號線程" ); private
final JTextArea oneArea = new
JTextArea( 5 ,
10 ); private
final JTextArea twoArea = new
JTextArea( 5 ,
10 ); } /** * 表示銀行的類 * */ class Bank{ public
Bank(){ } public
void deposit( int
money){ account += money; } public
int getAccount(){ return
account; } private
int account = 100 ; } /** * 表示往賬戶存錢 * */ class Transfer
implements Runnable{ public
Transfer(){ } public
Transfer(Bank bank, JTextArea textArea){ this .bank = bank; this .textArea = textArea; } @Override public
void run(){ for ( int
i = 0 ; i <
20 ; ++i){ bank.deposit( 10 ); String str = textArea.getText(); textArea.setText(str + "賬戶餘額爲:"
+ bank.getAccount() + "\n" ); } } private
Bank bank = null ; private
JTextArea textArea = null ; } |
因爲一個進程中的所有線程會共享進程中的資源,所以當一個線程還沒有將修改之後的結果保存的時候,另外一個線程卻進行讀取,這樣自然會產生錯誤,所以這個時候就需要我們採用同步來解決問題的。
使用同步方法實現同步
所謂同步方法,就是用synchronized修飾的方法,之所以這十幾個字母能解決困難的同步問題,這是和java中的內置鎖密切相關的,每一個java對象中有一個內置鎖,如果方法使用了synchronized進行修飾的話,內置鎖會保護整個方法,也就是在調用方法之前,需要、獲得內置鎖,否則就會處於阻塞狀態,當然這個關鍵字也可以修飾靜態方法,如果調用靜態方法,就會鎖住整個類
現在我們來看看例子,我們顯著需要修改Bank類中的deposit方法,修改爲:
1
2
3
|
public
synchronized void
deposit( int money){ account += money; } |
然後無論你的RP再好,在怎麼運行,也不會出現兩個文本域中出現一樣的問題、另外讀者可以思考一樣,爲什麼需要鎖住這個方法呢?其實:account += money;的執行是分3步運行的,先讀取account的值,然後計算account和money的和,最後在存入account中,在多線程中,有可能兩個線程同時讀取account的值,這樣就會少計算一次money的值。
至於修改之後的運行結果,我就不粘貼了。
但是要提醒一下大家,同步是一種高開銷的操作,所以應該儘量減少需要同步的內容
使用特殊域變量實現同步
上面的例子中採用synchronized這個關鍵字同步了那個方法,其實我們會發現,就本例子中,之所以出現同步問題的原因在於對於域account的讀取上,那麼我們就可以將account設置爲特殊域變量。使用關鍵字volatile
volatile提供了一種免鎖的機制,使用這個關鍵字修飾的域相當於告訴虛擬機,這個域可能會被其他的線程跟新,因此每次讀取這個域的時候都需要重新計算,而不是使用寄存器中的值,這個關鍵字不會提供任何的原子操作,也不能用來修飾final類型的變量、
現在我們修改Bank方法:
private volatile int account = 100;
我們只需要加一個關鍵字就行了。
提醒一下:關於安全域的併發訪問:
多線程中的非同步問題出現在對於域的讀寫上的時候,如果讓域自身避免這個問題的話,則不需要修改操作的方法,在java中有3中域自身就可以避免非同步問題:final域,使用volatile域,以及有鎖保護的域。
使用重入鎖實現線程同步
1
2
|
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; |
可以在程序中添加上面兩行代碼,。然後將Bank修改爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Bank{ public
Bank(){ } public
void deposit( int
money){ lock.lock(); try { account += money; } finally { lock.unlock(); } } public
int getAccount(){ return
account; } private
final Lock lock = new
ReentrantLock(); private
int account = 100 ; } |
這樣也可以解決非同步問題。至於這個類,大家可以自行去查看API,我只是在這裏提醒一下,如果synchronized能夠滿足需求的話,就使用synchronized關鍵字,因爲這個可以簡化代碼,如果需要更加高級的功能的時候,就使用Lock對象,在使用ReentrantLock的時候, 一定要注意及時釋放鎖,否則程序會出現死鎖。
使用線程局部變量實現線程同步
這個例子演示的是兩個線程同時修改一個變量,運行結果:
可以發現,每個線程完成修改之後的副本是完全獨立的,如果使用TreadLocal來管理變量,則每個使用這個變量的線程都會獲得這個變量的一個副本。,並且可以隨意修改這個副本,每個線程之間不會影響。
TreadLocal和同步機制都是爲了解決多線程中的相同變量訪問衝突的問題的,前者採用的是空間還時間,後者採用的是時間換空間
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Bank{ public
Bank(){ } public
void deposit( int
money){ account.set(account.get() + money); } public
int getAccount(){ return
account.get(); } private
static ThreadLocal<Integer> account = new
ThreadLocal<Integer>(){ @Override protected
Integer initialValue(){ return
100 ; } }; } |
線程之間的通信
還記得我在我的筆記java IO總結中給出了一個使用管道流進行線程之間通信的例子:
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
|
package Thread; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; /** * 使用管道流進行線程之間的通信 * */ public
class PipedTreadDemo{ public
static void main(String[] args){ Sender send = new
Sender(); Reciver rec = new
Reciver(); try { send.getOut().connect(rec.getReciver()); } catch (Exception e){ e.printStackTrace(); } new
Thread(send).start(); new
Thread(rec).start(); } } /** * 消息發送類 * */ class Sender
implements Runnable{ private
PipedOutputStream out = null ; public
Sender(){ out = new
PipedOutputStream(); } public
PipedOutputStream getOut(){ return
this .out; } @Override public
void run(){ String str = "rollen holt" ; try { out.write(str.getBytes()); } catch (IOException e){ e.printStackTrace(); } try { out.close(); } catch (IOException e){ e.printStackTrace(); } } } /** * 消息接受類 * */ class Reciver
implements Runnable{ private
PipedInputStream input = null ; public
Reciver(){ input = new
PipedInputStream(); } public
PipedInputStream getReciver(){ return
this .input; } @Override public
void run(){ byte [] bytes =
new
byte [ 1024 ]; int
len = 0 ; try { len = input.read(bytes); } catch (IOException e){ e.printStackTrace(); } try { input.close(); } catch (IOException e){ e.printStackTrace(); } System.out.println( "讀取的內容爲:"
+ new String(bytes,
0 , len)); } } |
【運行結果】:
讀取的內容爲:rollen holt
下面,我們在同步的前提下,在舉出一個線程通信的例子:
首先擺出運行結果再說:
程序代碼如下:
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
136
137
|
package Thread; /** * 線程之間的通信 * */ import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; public
class TreadCommunicate extends
JFrame{ public
TreadCommunicate(){ panel.setLayout( new
GridLayout( 2 ,
2 , 3 ,
3 )); panel.add(label1); panel.add(label2); JScrollPane js1 = new
JScrollPane(oneArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js1); JScrollPane js2 = new
JScrollPane(twoArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js2); statrButton.addActionListener( new
ActionListener(){ @Override public
void actionPerformed(ActionEvent e){ Sender sender = new
Sender(); Thread demo1 = new
Thread(sender); Thread demo2 = new
Thread( new Receiver(sender)); demo1.start(); demo2.start(); } }); panel2.add(panel); panel2.add(statrButton); setContentPane(panel2); setSize( 300 ,
400 ); setVisible( true ); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public
static void main(String[] args){ new
TreadCommunicate(); } /** * 賣家 * */ class
Sender implements
Runnable{ @Override public
void run(){ for ( int
i = 0 ; i <
5 ; ++i){ // 如果已經發送,那麼就等待 while (isValid){ Thread.yield(); } product = products[i]; String text = oneArea.getText(); oneArea.setText(text + "發送"
+ product + "\n" ); try { Thread.sleep( 1000 ); } catch (Exception e){ e.printStackTrace(); } isValid = true ; } } public
boolean isisValid(){ return
this .isValid; } public
void setValid( boolean
flag){ this .isValid = flag; } public
String getProduct(){ return
product; } private
volatile String[] products = {
"《***》" , "《紅樓夢》" ,
"《平凡的世界》" , "《流氓老師》" ,
"《西遊記》" }; private
volatile
boolean isValid = false ; private
volatile String product; } // end sender /** * 買家 * */ class
Receiver implements
Runnable{ public
Receiver(){ } public
Receiver(Sender sender){ this .sender = sender; } @Override public
void run(){ for ( int
i = 0 ; i <
5 ; ++i){ // 如果沒有發送,就等待 while (!sender.isisValid()){ Thread.yield(); } String test = twoArea.getText(); twoArea.setText(test + "接受到"
+ sender.getProduct() + "\n" ); try { Thread.sleep( 1000 ); } catch (Exception e){ e.printStackTrace(); } sender.setValid( false ); } } private
Sender sender; } JPanel panel = new
JPanel(); JPanel panel2 = new
JPanel(); private
final JButton statrButton = new
JButton( "開始交易" ); private
final JLabel label1 = new
JLabel( "賣家" ); private
final JLabel label2 = new
JLabel( "買家" ); private
final JTextArea oneArea = new
JTextArea( 5 ,
10 ); private
final JTextArea twoArea = new
JTextArea( 5 ,
10 ); } |
死鎖的範例
下面絕對不是本人蛋疼的寫出這個一個更加叫人蛋疼的程序。只是給出了一個例子:
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
|
/** * 簡單的死鎖 * */ public
class DeadLockDemo implements
Runnable{ @Override public
void run(){ // 獲得當前線程的名字 String str = Thread.currentThread().getName(); System.out.println(str + ": flag= "
+ flag); if (flag){ synchronized
(obj1){ try { Thread.sleep( 1000 ); } catch (Exception e){ e.printStackTrace(); } System.out.println(str + "已經進入同步快obj1,準備進入同步快obj2" ); synchronized
(obj2){ System.out.println(str + "已經進入同步快obj2" ); } } } if (!flag){ synchronized
(obj2){ try { Thread.sleep( 1000 ); } catch (Exception e){ e.printStackTrace(); } System.out.println(str + "已經進入同步快obj2,準備進入同步快obj1" ); synchronized
(obj1){ System.out.println(str + "已經進入同步快obj1" ); } } } } public
static void main(String[] args){ DeadLockDemo demo1 = new
DeadLockDemo(); DeadLockDemo demo2 = new
DeadLockDemo(); demo1.flag = true ; demo2.flag = false ; new
Thread(demo1).start(); new
Thread(demo2).start(); } private
boolean flag; private
final Object obj1 = new
Object(); private
final Object obj2 = new
Object(); } |
【運行結果】
(我承認我今天RP爆發,我運行了10次,還是沒出現死鎖那種情況,但是這個程序確實可以產生死鎖的,哪位運行這個程序,要是產生了死鎖,麻煩說一下,謝謝)
使用線程池優化多線程編程
這個例子使用的是Executors類,讀者自行查看API,因爲要解說的話,就太多了。
下面這個例子給出了使用線程池和不使用線程池的情況下的效率的問題。
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
|
package Thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 使用線程池優化多線程編程 * */ public
class ThreadPoolDemo{ public
static void main(String[] args){ Runtime run = Runtime.getRuntime(); // 爲了減少誤差 run.gc(); long
currentTime = System.currentTimeMillis(); long
freemonery = run.freeMemory(); for ( int
i = 0 ; i <
10000 ; ++i){ new
Thread( new Temo()).start(); } System.out.println( "獨立運行10000個線程佔用內存爲:" + (freemonery - run.freeMemory())); System.out.println( "獨立運行10000個線程佔用時間爲:" + (System.currentTimeMillis() - currentTime)); // 下面使用線程池來試試 run.gc(); freemonery = run.freeMemory(); currentTime = System.currentTimeMillis(); ExecutorService executorService = Executors.newFixedThreadPool( 3 ); for ( int
i = 0 ; i <
10000 ; ++i){ executorService.submit( new
Temo()); } System.out.println( "使用線程池運行10000個線程佔用內存爲:" + (freemonery - run.freeMemory())); System.out.println( "使用線程池運行10000個線程佔用時間爲:" + (System.currentTimeMillis() - currentTime)); } } class Temo implements
Runnable{ @Override public
void run(){ count++; } private
int count = 0 ; } |
【運行結果】:
獨立運行10000個線程佔用內存爲:3490440
獨立運行10000個線程佔用時間爲:1808
使用線程池運行10000個線程佔用內存爲:1237424
使用線程池運行10000個線程佔用時間爲:62
關於哲學家就餐的問題
由於代碼比較長,所以單獨列出爲一篇文章
地址:http://www.cnblogs.com/rollenholt/archive/2011/09/15/2178004.html
使用信號量實現線程同步
現在我們繼續回答之前銀行存款的問題,相信大家還沒有忘記,哈哈,真是不好意思,本來線程同步這一塊應該整理在一起的。
一個信號量有3中操作,而且他們全部都是原子的,初始化,增加,減少。增加可以爲一個進程解除阻塞,減少可以爲一個進程進入阻塞。
Semaphore類是一個技術信號量,從概念上信號量維持了一個許可集。
現在我們繼續看上面的銀行存款問題:
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
|
package Thread; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.concurrent.Semaphore; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; public
class synDemo extends
JFrame{ public
synDemo(){ panel.setLayout( new
GridLayout( 2 ,
2 , 3 ,
3 )); panel.add(label1); panel.add(label2); JScrollPane js1 = new
JScrollPane(oneArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js1); JScrollPane js2 = new
JScrollPane(twoArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js2); panel2.add(panel); panel2.add(statrButton); setContentPane(panel2); statrButton.addActionListener( new
ActionListener(){ @Override public
void actionPerformed(ActionEvent e){ Thread demo1 = new
Thread( new Transfer1(bank, oneArea, semaphore)); demo1.start(); Thread demo2 = new
Thread( new Transfer1(bank, twoArea, semaphore)); demo2.start(); } }); setSize( 300 ,
400 ); setVisible( true ); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public
static void main(String[] args){ new
synDemo(); } Semaphore semaphore = new
Semaphore( 1 ,
true ); private
final Bank1 bank = new
Bank1(); JPanel panel = new
JPanel(); JPanel panel2 = new
JPanel(); private
final JButton statrButton = new
JButton( "開始存錢" ); private
final JLabel label1 = new
JLabel( "一號線程" ); private
final JLabel label2 = new
JLabel( "二號線程" ); private
final JTextArea oneArea = new
JTextArea( 5 ,
10 ); private
final JTextArea twoArea = new
JTextArea( 5 ,
10 ); } /** * 表示銀行的類 * */ class Bank1{ public
Bank1(){ } public
void deposit( int
money){ account += money; } public
int getAccount(){ return
account; } private
int account; } /** * 表示往賬戶存錢 * */ class Transfer1
implements Runnable{ public
Transfer1(){ } public
Transfer1(Bank1 bank, JTextArea textArea, Semaphore semaphore){ this .bank = bank; this .textArea = textArea; this .semaphore = semaphore; } @Override public
void run(){ for ( int
i = 0 ; i <
20 ; ++i){ // 獲得許可 try { semaphore.acquire(); } catch (InterruptedException e){ e.printStackTrace(); } bank.deposit( 10 ); String str = textArea.getText(); textArea.setText(str + "賬戶餘額爲:"
+ bank.getAccount() + "\n" ); // 釋放許可 semaphore.release(); } } // 注意 private
Semaphore semaphore; private
Bank1 bank = null ; private
JTextArea textArea = null ; } |
運行結果:
使用原子變量實現線程同步
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
|
package Thread; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; public
class synDemo extends
JFrame{ public
synDemo(){ panel.setLayout( new
GridLayout( 2 ,
2 , 3 ,
3 )); panel.add(label1); panel.add(label2); JScrollPane js1 = new
JScrollPane(oneArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js1); JScrollPane js2 = new
JScrollPane(twoArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(js2); panel2.add(panel); panel2.add(statrButton); setContentPane(panel2); statrButton.addActionListener( new
ActionListener(){ @Override public
void actionPerformed(ActionEvent e){ Thread demo1 = new
Thread( new Transfer1(bank, oneArea)); demo1.start(); Thread demo2 = new
Thread( new Transfer1(bank, twoArea)); demo2.start(); } }); setSize( 300 ,
400 ); setVisible( true ); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public
static void main(String[] args){ new
synDemo(); } private
final Bank1 bank = new
Bank1(); JPanel panel = new
JPanel(); JPanel panel2 = new
JPanel(); private
final JButton statrButton = new
JButton( "開始存錢" ); private
final JLabel label1 = new
JLabel( "一號線程" ); private
final JLabel label2 = new
JLabel( "二號線程" ); private
final JTextArea oneArea = new
JTextArea( 5 ,
10 ); private
final JTextArea twoArea = new
JTextArea( 5 ,
10 ); } /** * 表示銀行的類 * */ class Bank1{ public
Bank1(){ } public
void deposit( int
money){ account.addAndGet(money); } public
int getAccount(){ return
account.get(); } //注意 private
final AtomicInteger account = new
AtomicInteger( 100 ); } /** * 表示往賬戶存錢 * */ class Transfer1
implements Runnable{ public
Transfer1(){ } public
Transfer1(Bank1 bank, JTextArea textArea){ this .bank = bank; this .textArea = textArea; } @Override public
void run(){ for ( int
i = 0 ; i <
20 ; ++i){ bank.deposit( 10 ); String str = textArea.getText(); textArea.setText(str + "賬戶餘額爲:"
+ bank.getAccount() + "\n" ); } } private
Bank1 bank = null ; private
JTextArea textArea = null ; } |
【運行結果】: