201521044091 《Java程序設計》第11周學習總結
1. 本章學習總結
2. 書面作業
Q1.1.互斥訪問與同步訪問
完成題集4-4(互斥訪問)與4-5(同步訪問)
1.1 除了使用synchronized修飾方法實現互斥同步訪問,還有什麽辦法實現互斥同步訪問(請出現相關代碼)?
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
synchronized void deposit(int money){
lock.lock();
try{
balance+=money;
condition.signal();
}finally {
lock.unlock();
}
}
synchronized void withdraw(int money){
lock.lock();
try{
while(balance<money){
try{
condition.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
balance-=money;
}finally {
lock.unlock();
}
}
1.2 同步代碼塊與同步方法有何區別?
- 同步方法是直接在方法上加synchronized實現加鎖,而同步代碼塊則是在方法內部通過
synchronized(this){}
實現加鎖。因為同步方法鎖的範圍比較大,一般同步範圍越大性能越差,如果只是讓某些個線程不能同時訪問方法內部的部分代碼的話,應該采取同步代碼,這樣訪問對象的時間性能就會比較好。 - 舉個例子
public synchronized void test1() {
System.out.println("hello");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2() {
synchronized(this) {
System.out.println("hello world");
}
}
//hello world隔了五秒才運行,從這段代碼可以看出如果將synchronized放在方法上,同步鎖的範圍是整個方法,所以test2想要獲得對象鎖必須等test1的方法全部執行完,也就是需要在休眠五秒後才能得到鎖。
1.3 實現互斥訪問的原理是什麽?請使用對象鎖概念並結合相應的代碼塊進行說明。當程序執行synchronized同步代碼塊或者同步方法時,線程的狀態是怎麽變化的?
-
每個對象都有一把鎖,只有獲得this對象上的內部鎖,才可以訪問該段資源,沒有獲得對象鎖的線程在Lock Pool中,等待獲得對象鎖的線程運行完解鎖,然後通過競爭獲得對象鎖再運行。也就是一段時間內只允許一個線程訪問這段共享資源。
class Counter { private static int id = 0; //當有線程1先對其調用addId方法,另一線程2調用subtractId方法, public static synchronized void addId() { id++; //線程1獲得對象鎖(線程2鎖池中等待獲得鎖)對其id++,執行完後,釋放this上的對象鎖, } public static synchronized void subtractId() { id--; //另一線程2獲得對象鎖,對其id--,然後釋放對象鎖。 } public static int getId() { return id; } }
-
多個線程start(),競爭對象鎖,某一線程競爭獲得對象鎖,其余線程在等待池中等待線程釋放對象鎖,當線程釋放對象鎖其余線程又開始競爭對象鎖,直到程序結束。
1.4 Java多線程中使用什麽關鍵字實現線程之間的通信,進而實現線程的協同工作?為什麽同步訪問一般都要放到synchronized方法或者代碼塊中?
- 使用wait ()、notify()、notifyAll()實現線程之間的通信
- 因為將共享資源放在synchronized方法或者代碼塊中就可以保證獲得對象鎖的線程可以在對共享資源進行操作的時候不被打擾,其他線程只能在其之後才能訪問,避免了多個線程訪問同一資源引起的沖突。
Q2.交替執行
2.1 實驗總結
public synchronized void jiaoti1(){
while(getSize()>0){
if(flag){
try{
wait();
}catch (Exception e) {
// TODO: handle exception
}
}
else {
System.out.println(Thread.currentThread().getName()+" finish "+str[i]);
i++;
flag=true;
notify();
}
}
}
...
- 因為兩線程交替執行所以需要wait()、notify()的協作。設置flag初始值為flase,執行了Woker1,置flag為true,喚醒Woker2。當Woker2執行完,再置flag為flase,喚醒Woker1,直到任務數為空。
Q3.互斥訪問
3.1 修改TestUnSynchronizedThread.java源代碼使其可以同步訪問。(關鍵代碼截圖,需出現學號)
//201521044091
public static synchronized void addId() {
id++;
}
public static synchronized void subtractId() {
id--;
}
3.2 進一步使用執行器改進相應代碼(關鍵代碼截圖,需出現學號)
int n=3;
List<Callable<Object>> listtask=new ArrayList<>(); //創建Callable<Object>類型的listtask
ExecutorService exec=Executors.newCachedThreadPool();
for (int i = 0; i < n; i++) {
listtask.add(Executors.callable(new Adder())); //將返回的Callable對象加入listtask
}
for (int i = 0; i < n; i++) {
listtask.add(Executors.callable(new Subtracter()));
}
exec.invokeAll(task); // 使用ExecutorService接口的invokeAll(listTask)
System.out.println(Counter.getId());
System.out.println("main end");
Q4.線程間的合作:生產者消費者問題
4.1 運行MyProducerConsumerTest.java。正常運行結果應該是倉庫還剩0個貨物。多運行幾次,觀察結果,並回答:結果正常嗎?哪裏不正常?為什麽?
- 不正常,有時會出現剩余貨物還有10個,問題出現在
public synchronized void add(String t)
和public synchronized void remove()
。雖然這兩個方法實現了synchronized,但是倉庫只有一個當往倉庫存入一個數據,而生產者和消費者的存取速度不一樣,則有可能出現生產者比消費者快,消費者來不及取數據,或者反之,消費者取不到數據。
4.2 使用synchronized, wait, notify解決該問題(關鍵代碼截圖,需出現學號)
public synchronized void add(String t) {
while(repo.size()>=capacity){
try{
wait();
System.out.println("倉庫已滿!無法添加貨物。");
}catch (Exception e) {
// TODO: handle exception
}
}
repo.add(t);
notify();
}
public synchronized void remove() {
while(repo.size()<=0){
try{
wait();
System.out.println("倉庫無貨!無法從倉庫取貨");
}catch (Exception e) {
// TODO: handle exception
}
}
repo.remove(0);
notify();
}
4.3 選做:使用Lock與Condition對象解決該問題。
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public synchronized void add(String t) {
lock.lock();
try{
while(repo.size()>=capacity){
try{
condition.await();
System.out.println("倉庫已滿!無法添加貨物。");
}catch (Exception e) {
// TODO: handle exception
}
}
repo.add(t);
condition.signal();
}finally {
lock.unlock();
}
}
public synchronized void remove() {
try{
while(repo.size()<=0){
try{
condition.await();
System.out.println("倉庫無貨!無法從倉庫取貨");
}catch (Exception e) {
// TODO: handle exception
}
}
repo.remove(0);
condition.signal();
}finally {
lock.unlock();
}
}
Q5.查詢資料回答:什麽是線程安全?(用自己的話與代碼總結,寫自己看的懂的作業)
-
什麽是線程安全:
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
-
Q6.選做:實驗總結
6.1 4-8(CountDownLatch)實驗總結
- 創建等待的CountDownLatch對象,因為CountDownLatch類是一個同步計數器,構造時傳入int參數,該參數就是計數器的初始值,所以
CountDownLatch Latch=new CountDownLatch(n);
。然後調用Executors.newFixedThreadPool();
創建固定線程數的線程池。 - countDown()方法,計數器減1,如果計數器沒有達0,那麽await()方法會阻塞程序繼續執行,
latch.await();
可以讓主線程等待子線程的結束。
3. PTA實驗總結及碼雲上代碼提交記錄
3.1本周Commit歷史截圖
3.2PTA實驗
201521044091 《Java程序設計》第11周學習總結