1. 本周學習總結
1.1 以你喜歡的方式(思維導圖或其他)歸納總結多線程相關內容。
2. 書面作業
本次PTA作業題集多線程
Q1.互斥訪問與同步訪問
完成題集4-4(互斥訪問)與4-5(同步訪問)
1.1 除了使用synchronized修飾方法實現互斥同步訪問,還有什么辦法實現互斥同步訪問(請出現相關代碼)?
1.2 同步代碼塊與同步方法有何區別?
1.3 實現互斥訪問的原理是什么?請使用對象鎖概念并結合相應的代碼塊進行說明。當程序執行synchronized同步代碼塊或者同步方法時,線程的狀態是怎么變化的?
1.4 Java多線程中使用什么關鍵字實現線程之間的通信,進而實現線程的協同工作?為什么同步訪問一般都要放到synchronized方法或者代碼塊中?
1.1、可以使用Lock對象和Condition對象實現
class Account{private balance;private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public Account(int balance) {super();this.balance = balance;}public int getBalance() {return balance;}public void deposit(int money){try{lock.lock();this.balance=getBalance()+money;condition.signalAll();}finally{lock.unlock();}}public void withdraw(int money){try{lock.lock();while(getBalance()==0){try {condition.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}condition.signalAll();this.balance=getBalance()-money;}finally{lock.unlock();}}
}
1.2、答:同步方法直接在方法上加synchronized實現加鎖,同步代碼塊則在方法內部加鎖,很明顯,同步方法鎖的范圍比較大,而同步代碼塊范圍要小點,一般同步的范圍越大,性能就越差,一般需要加鎖進行同步的時候,肯定是范圍越小越好,這樣性能更好。
1.3、答:
原理:當資源被訪問,為其上鎖,阻止其他線程訪問,停止訪問時,解鎖,其他線程可以訪問
public synchronized void deposit(int money){//synchronized修飾this.balance=getBalance()+money;notifyAll();}public synchronized void withdraw(int money){//synchronized修飾while(getBalance()==0){try {wait();} catch (InterruptedException e) {e.printStackTrace();}}this.balance=getBalance()-money;notifyAll();}
在代碼中若無synchronized修飾,可能有多個線程通過deposit或withdraw方法同時存取Account對象的balance屬性。
使用synchronized修飾后
1.線程A執行deposit,balance上鎖,線程B wait,notifyAll之后,balance解鎖
2.線程B執行withdraw,balance上鎖,線程A無法訪問,notifyAll之后,balance解鎖
1.4、答:用wait()、notify()來實現線程之間的協作,放到synchronized方法或者代碼塊是為了防止多個線程訪問同一資源所引起的沖突。
Q2.交替執行
實驗總結
答:要實現交替訪問,首先要將方法全部都用synchronized來修飾,確保所有變量都持有鎖。其次線程之間的協作可以使用wait()和notify()并配合上boolean變量來完成,要做到交替執行,需要中間橋梁來確認是否執行該任務,這個橋梁就是flag。
Q3.互斥訪問
3.1 修改TestUnSynchronizedThread.java源代碼使其可以同步訪問。(關鍵代碼截圖,需出現學號)
3.2 進一步使用執行器改進相應代碼(關鍵代碼截圖,需出現學號)
參考資料:Java多線程之Executor、ExecutorService、Executors、Callable、Future與FutureTask
3.1
//201521145048
public static synchronized void addId() {id++;
}
public static synchronized void subtractId() {id--;
}
3.2
//201521145048
List<Callable<Object>> task=new ArrayList<>();
ExecutorService exec=Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {task.add(Executors.callable(new Adder())); }
for (int i = 0; i < 3; i++) {task.add(Executors.callable(new Subtracter()));}
exec.invokeAll(task);
System.out.println(Counter.getId());
System.out.println("main end");
Q4.線程間的合作:生產者消費者問題
4.1 運行MyProducerConsumerTest.java。正常運行結果應該是倉庫還剩0個貨物。多運行幾次,觀察結果,并回答:結果正常嗎?哪里不正常?為什么?
4.2 使用synchronized, wait, notify解決該問題(關鍵代碼截圖,需出現學號)
4.3 選做:使用Lock與Condition對象解決該問題。
4.1、答:結果不正常,剩余個數從0到10皆有,因為生產者與消費者的存取速度不一致,所以會導致生產和消費不一致
4.2、答:
//201521145048
public synchronized void add(String t) {while(repo.size() == capacity) {System.out.println("倉庫已滿!無法添加貨物。");try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}} repo.add(t);notifyAll();
}
public synchronized void remove() {while (repo.size() == 0) {System.out.println("倉庫無貨!無法從倉庫取貨");try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}} repo.remove(0);notifyAll();
}
4.3、答:
//201521123023
private Lock lock=new ReentrantLock();
private Condition condition = lock.newCondition();
public synchronized void add(String t) {try{lock.lock();while(repo.size() == capacity) {System.out.println("倉庫已滿!無法添加貨物。");try {condition.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}repo.add(t);condition.signalAll(); }finally{lock.unlock();}}
public synchronized void remove() {try{lock.lock();while (repo.size() == 0) {System.out.println("倉庫無貨!無法從倉庫取貨");try {condition.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}} repo.remove(0);condition.signalAll();}finally{lock.unlock();}}
Q5.查詢資料回答:什么是線程安全?(用自己的話與代碼總結,寫自己看的懂的作業)
答:
線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的,也就是線程同步
例如
public synchronized static void addId() {id++;
}public synchronized static void subtractId() {id--;
}
多個線程同時運行以上代碼,synchronized關鍵字使得線程同步,多個線程運行結果和單個線程運行結果一致,都為0,
3. 碼云上代碼提交記錄
題目集:多線程(4-4到4-10)
3.1. 碼云代碼提交記錄
在碼云的項目中,依次選擇“統計-Commits歷史-設置時間段”, 然后搜索并截圖