多執行緒-作業練習

魔笛手發表於2019-01-09

1. 寫2個執行緒,其中一個執行緒列印1~52,另一個執行緒列印A~Z,列印順序應該是12A34B56C…5152Z。該題需要用到多執行緒通訊的知識。
解題思路:首先從最簡單的功能著手,先思考如何寫一個方法列印1~52,再寫一個方法列印A~Z。

列印1~52 的方法方法簡單,可以通過一個for迴圈來解決:

for (int i=1 ; i<52; i++)
{
System.out.print(i);
}

列印A~Z的方法一開始迷了一下,後來參考網上的方法,用char[]陣列來實現,比列印數字稍微複雜一些,先建立一個長度為26的char[]陣列,然後用ASSCAL碼對其賦值,型別轉換為字元型:

char[] zimu = new char[26];
for (int i = 0; i < 26; i++)
{
zimu[i] = (char) (i + 65);
}

==然後通過foreach迴圈來遍歷迴圈輸出該陣列的元素==

for(char c : zimu)
{
System.out.print(c);
}

然後考慮建立一個包含這兩種方法的類,

public class PrintStore {
//這裡一開始我使用public修飾flag變數,後來想了一下,這個變數值只應該被該類自身訪問修改,所以改為了private
//通過一個標識flag來確保列印字母的執行緒一定要等到列印數字執行緒執行後才得到執行機會。
private boolean flag = false;

public synchronized void printNumber() {
// System.out.printf(“列印數字執行緒啟動,此時flag為 %s
“, flag);

for (int i = 1; i < 53; i++) {
System.out.print(i);
if (i % 2 == 0) {
flag = true;
notifyAll();
try {
wait();

} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public synchronized void printLetter() {
// System.out.printf(“列印字母執行緒啟動,此時flag為 %s
“, flag);
char[] zimu = new char[26];
for (int i = 0; i < 26; i++) {
zimu[i] = (char) (i + 65);
}

for (char c : zimu) {
if (flag) {
System.out.print(c);
flag = false;
notify();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

然後寫兩個執行緒類

class PrintNumber implements Runnable {
private PrintStore ps;
public PrintNumber(PrintStore ps) {
this.ps=ps;
}
@Override
public void run() {
ps.printNumber();
}
}

class PrintLetter implements Runnable {
private PrintStore ps;
public PrintLetter(PrintStore ps) {
this.ps=ps;
}
@Override
public void run() {
ps.printLetter();
}

}

最後是寫主程式,也就是main方法所在的程式,其實功能就是給一個程式的入口,建立執行緒,啟動執行緒

public class HomeWork1 {

public static void main(String[] args) {
PrintStore ps=new PrintStore();
PrintNumber pn=new PrintNumber(ps);
PrintLetter pl=new PrintLetter(ps);
new Thread(pn).start();
new Thread(pl).start();
}

}

寫這個程式過程中遇到了不少問題,一開始我建立執行緒時,沒有用到共享資源,導致兩個執行緒都是獨立的,然後發現執行緒A的wait()方法和notify()方法,只能暫停和啟動A執行緒本身,並不會通知B執行緒啟動。後來仔細研究了一下這裡的問題:

Object類的wait(),notify(),notifyAll()方法必須由同步監視器物件來呼叫。
* 對於使用synchronized修飾的同步方法,因為該類的預設例項(this)就是同步監視器,所以可以在同步方法中直接呼叫這三個方法;
* 對於使用synchronized修飾的同步程式碼塊,同步監視器是synchronized後括號裡的物件,所以必須使用該物件呼叫。

這三個方法,都只能對同一個同步監視器上的執行緒有效,所以我最開始的那種方式,其實存在著兩個同步監視器,執行緒A和執行緒B各自有各自的同步監視器,所以A不能成功通知到B。後來參考這個:
深入理解Java多執行緒之執行緒間的通訊方式
發現兩個執行緒之間如果想要成功的進行通訊,必須要建立一個執行緒之間的共享資源,然後把這個共享資源當做同步監視器。

使用synchronized關鍵字同步來實現執行緒間的通訊
public class MyObject {

synchronized public void methodA() {
//do something….
}

synchronized public void methodB() {
//do some other thing
}
}

public class ThreadA extends Thread {

private MyObject object;
//省略構造方法
@Override
public void run() {
super.run();
object.methodA();
}
}

public class ThreadB extends Thread {

private MyObject object;
//省略構造方法
@Override
public void run() {
super.run();
object.methodB();
}
}

public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();

//執行緒A與執行緒B 持有的是同一個物件:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}

由於執行緒A和執行緒B持有同一個MyObject類的物件object,儘管這兩個執行緒需要呼叫不同的方法,但是它們是同步執行的,比如:執行緒B需要等待執行緒A執行完了methodA()方法之後,它才能執行methodB()方法。這樣,執行緒A和執行緒B就實現了 通訊。

這種方式,本質上就是“共享記憶體”式的通訊。多個執行緒需要訪問同一個共享變數,誰拿到了鎖(獲得了訪問許可權),誰就可以執行。

還遇到了程式列印結果是12A4C68E..這樣的情況,是程式碼的迴圈邏輯出了問題,在for迴圈中我只讓它在滿足條件時列印出來,卻忘了不滿足條件時這個迴圈其實也在進行著,所以出現了這種隔著列印的情況。

2. 假設車庫有3個車位(可以通過boolean[]陣列來表示車庫)可以停車,寫一個程式模擬多個使用者開車離開,停車入庫的效果。注意:車位有車時不能停車。
這道題也想了很久,使用陣列來表示車庫的話,我的思路大概是這樣的
寫一個Car類,裡面定義了車庫,還有停車,開車的方法.
但是這裡面出現了很多想不通的問題,陣列標識車庫的話,每當有車駛入,駛出時,怎麼修改陣列中相應元素的狀態。覺得很麻煩,想參考別人的做法,發現他用的是BlockingQueue阻塞佇列來完成的,確實利用阻塞佇列的特點這道題很容易,==因為阻塞佇列就是佇列已滿時,再試圖新增元素就會自動執行緒阻塞。佇列已空時試圖取出元素也會自動阻塞。==

public class Car {
// 使用boolean陣列表示停車場,FALSE表示車位空,可停車,TRUE表示車位有車
private boolean[] Park = new boolean[] { false, false, false };

int CarNo;
//重寫toString方法,否則列印出來的是hash地址值
@Override
public String toString() {
return (CarNo + “號車”);
};
//寫了有引數的構造器後系統將不再主動提供無引數構造器,自己寫
public Car() {
}

public Car(int CarNo) {
this.CarNo = CarNo;
}

public synchronized void CarIn(Car c) throws InterruptedException {
for (boolean b : Park) {

if (b == false) {
System.out.println(“有空車位,” + c.toString() + “已停入停車場”);
b = true;

}
else {
System.out.println(“車位已滿,請耐心等待”);
wait();
}

}
}

public synchronized void CarOut(Car c) {

System.out.println(c.toString() + “已離開停車場”);

notifyAll();
}
}

這是最初嘗試使用陣列來模擬停車場,未成功完成。可以參考網上這篇部落格 陣列模擬停車場 但是這個程式寫的太複雜了,他還考慮到了每輛車停進去使用的是哪個車位。
從他這個程式中想到了另一種思路: 只用一個state成員變數來定義停車場中剩餘的車位數,這樣可以簡化程式。

public class Park {

private int state = 3;

public synchronized void CarIn(int i) {

try {
while (state == 0) {
System.out.println(“目前空餘車位為 ” + state + ” 請等待”);
wait();
}
System.out.println(i+”號車停車成功”);
state = state – 1;
System.out.println(“目前剩餘車位: ” + state);
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public synchronized void CarOut(int i) {

try {
while (state == 3) {
wait();
}
System.out.println(i +”號車已駛出”);
state = state + 1;
System.out.println(“目前剩餘車位: ” + state);
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

public class CarInThread extends Thread{
Park park=new Park();
public CarInThread(Park park) {
this.park=park;
}

@Override
public void run() {
super.run();
for(int i=1;i<20;i++){
park.CarIn(i);
}
}
}

 

public class CarOutThread extends Thread{
Park park=new Park();
public CarOutThread(Park park) {
this.park=park;
}
@Override
public void run() {
super.run();
for(int i=1;i<20;i++){
park.CarOut(i);
}
}
}

 

public class HomeWork2 {
public static void main(String[] args){
Park park =new Park();
CarInThread cInThread=new CarInThread(park);
CarOutThread cOutThread=new CarOutThread(park);
new Thread(cInThread).start();
new Thread(cOutThread).start();

}
}

這樣就算是成功完成了這道題,輸出結果是這樣的:
1號車停車成功
目前剩餘車位: 2
1號車已駛出
目前剩餘車位: 3
2號車停車成功
目前剩餘車位: 2
3號車停車成功
目前剩餘車位: 1
4號車停車成功
目前剩餘車位: 0
目前空餘車位為 0 請等待
2號車已駛出
目前剩餘車位: 1
3號車已駛出
目前剩餘車位: 2
4號車已駛出
目前剩餘車位: 3
5號車停車成功
目前剩餘車位: 2
6號車停車成功
目前剩餘車位: 1
7號車停車成功
目前剩餘車位: 0
目前空餘車位為 0 請等待
5號車已駛出
目前剩餘車位: 1

改為使用Condition控制執行緒通訊:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Park {
private Lock lock=new ReentrantLock();
private Condition con=lock.newCondition();
private int state = 3;

public void CarIn(int i) {
lock.lock();
try {
while (state == 0) {
System.out.println(“目前空餘車位為 ” + state + ” 請”+i+”號車等待”);
con.await();
}
System.out.println(i+”號車停車成功”);
state = state – 1;
System.out.println(“目前剩餘車位: ” + state);
con.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}

public void CarOut(int i) {
lock.lock();
try {
while (state == 3) {
con.await();
}
System.out.println(i +”號車已駛出”);
state = state + 1;
System.out.println(“目前剩餘車位: ” + state);
con.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}

}

原文:https://blog.csdn.net/oh_mygarden/article/details/53648665
版權宣告:本文為博主原創文章,轉載請附上博文連結!

相關文章