多線程

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的值,然後計算accountmoney的和,最後在存入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;
}

【運行結果】:




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章