使用Runnable接口創建多線程
- 適合多個相同的程序代碼的線程去處理同一資源的情況,把虛擬CPU(線程)同程序的代碼、數據有效的分離,較好地體現了面向對象的設計思想
- 可以避免由於java的單繼承特性帶來的侷限。我們經常碰到這樣一種情況,即當我們要將已經繼承了某一個類的之類放入多線程中,由於一個類不可能同時有兩個父類,所以不能用繼承Thread類的方式,那麼,這個類就只能採用實現Runnable
- 當線程被構造時,需要的代碼和數據通過一個對象作爲構造函數實參傳遞進去,這個對象就是實現了Runnable接口的類的實例。
- 事實上,幾乎所有多線程應用都可用Runnable接口方式。
package blackhouse.thread;
public class ThreadDemo1 {
public static void main(String[] args) {
TestThread tt = new TestThread();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
}
}
class TestThread implements Runnable {
int tickets = 100;
@Override
public void run() {
while (true) {
synchronized (str) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket" + tickets--);
}
}
}
}
}
多線程在實際中的應用
- 網絡聊天程序的收發
- 表記錄的中途取消
- www服務器爲每一個來訪者都建立專屬服務
線程同步問題
上面的票號可能出現負數
1.解決的辦法是使用synchronized語句塊——同步代碼塊
將上面的部分代碼重寫如下:
String str = new String("");
@Override
public void run() {
while (true) {
synchronized (str) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket" + tickets--);
}
}
}
}
線程同步的原理:
str這個對象也被稱爲監視器,並且多個線程的這個監視器應該是同一個。
所以這個String str = new String("");這一段不能放在run()方法中。
首先我們規定str這個對象的標誌位爲1時不可以執行下面的程序片,爲0時可以。
synchronized 關鍵字把str對象的標誌位置爲0,第一個線程進入時得到鎖旗標,並把對象標誌置爲0,其他線程再進入時進入線程池等待第一個線程歸還其鎖旗標,然後再執行代碼片。
在這個程序中,同步會犧牲程序的性能。因爲在一點時間點上,CPU只能執行一個線程,但是同步會消耗系統線程調度時間。
除了使用同步代碼塊外,我們還可以使用同步函數。
我們把run方法中的if語句塊抽取出來構造一個sale()方法如下:
public synchronized void sale() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket" + tickets--);
}
}
然後再在run()方法中調用這個方法也可以達到線程同步。
public void run() {
while (true) {
sale();
}
}
現在我們來想一想這個問題,我們知道同步機制是通過對象的標誌位來實現的,那麼我們定義的sale()方法是哪個對象的標誌位呢?我們不妨看看下面的這段代碼:
package blackhouse.thread;
public class ThreadDemo1 {
public static void main(String[] args) {
TestThread tt = new TestThread();
new Thread(tt).start();
tt.str="method";
new Thread(tt).start();
}
}
class TestThread implements Runnable {
int tickets = 100;
String str = new String("");
@Override
public void run() {
if (str.equals("method")) {
while (true) {
sale();
}
} else {
while (true) {
synchronized (str) {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket" + tickets--);
}
}
}
}
}
public synchronized void sale() {
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " is saling ticket" + tickets--);
}
}
}
可能你會認爲這個程序的線程是不同步的,我們來看看執行的結果
Thread-0 is saling ticket100
Thread-0 is saling ticket99
Thread-1 is saling ticket98
Thread-1 is saling ticket97
Thread-1 is saling ticket……
Thread-1 is saling ticket2
Thread-1 is saling ticket1
我們發現這兩個線程是同步的。並不是一個執行同步代碼塊,一個執行同步方法,他們可能都是執行的同步代碼塊或都是執行的同步方法。如果他們都是執行的同步方法,那麼他們的同步監視器都是使用的this對象,如果他們都是執行的同步代碼塊,那麼他們都是使用的str對象作爲監視器。那麼他究竟是執行了那塊synchronized 呢?
我們接下來在sale()方法中加入一條語句以示區別:
System.out.print("sale()");
System.out.println(Thread.currentThread().getName()+" is saling ticket " + tickets--);
結果輸出如下:
sale()Thread-0 is saling ticket100
sale()Thread-0 is saling ticket……
sale()Thread-1 is saling ticket2
sale()Thread-1 is saling ticket1
那麼就是說 if (str.equals("method")) 這條語句一開始就是成立的。要知道我們只是在第一個線程啓動之後把str 改爲method。
new Thread(tt).start(); 語句只是說明這個線程出於就緒狀態。main()線程可能還在往下面執行tt.str="method";
我們來將主線程暫停一下看看:
TestThread tt = new TestThread();
new Thread(tt).start();
try
{
Thread.sleep(1);
} catch (InterruptedException e)
{
e.printStackTrace();
}
tt.str = "method";
new Thread(tt).start();
輸出結果:
Thread-0 is saling ticket100
sale()Thread-1 is saling ticket99
Thread-0 is saling ticket98
sale()Thread-1 is saling ticket97
Thread-0 is saling ticket35
sale()Thread-1 is saling ticket34
……………………………………
sale()Thread-1 is saling ticket6
Thread-0 is saling ticket5
sale()Thread-1 is saling ticket4
sale()Thread-1 is saling ticket3
Thread-0 is saling ticket2
sale()Thread-1 is saling ticket1
Thread-0 is saling ticket0
最後輸出的是ticket0,說明線程不同步。和我們最初的推理相同,我們再來把同步代碼塊的str改爲this,輸出結果爲:
Thread-0 is saling ticket100
Thread-0 is saling ticket99
sale()Thread-1 is saling ticket98
……………………
sale()Thread-1 is saling ticket90
Thread-0 is saling ticket89
Thread-0 is saling ticket88
……………………
Thread-0 is saling ticket3
Thread-0 is saling ticket2
Thread-0 is saling ticket1——————>最後爲ticket1
我們發現又是同步的了,這樣代碼塊與函數就同步了。說明sale()函數的監視器對象就是this 。