Java多線程入門(二)

會當凌絕頂,一覽衆山小

| @Author:TTODS

Java多線程入門(二)

線程同步

爲什麼要線程同步?

使用多線程可以讓我們的程序更加充分的利用CPU,提高程序的效率,但是同時也帶來了一些問題。多線程在使用線程公共資源的時候往往會遇到問題。
想象一下這樣一個情景:一個商店中有三件商品,但是四個顧客同時來購買商品,他們都能買到商品嗎?
在這裏插入圖片描述
我們用代碼模擬一下:

public class Test{
       public static void main(String[] args) {
             //創建一個商店
             Store store = new Store();
             //創建四個共用一個商店的顧客線程
             CustomerThread ct1 = new CustomerThread("顧客1",store);
             CustomerThread ct2 = new CustomerThread("顧客2",store);
             CustomerThread ct3 = new CustomerThread("顧客3",store);
             CustomerThread ct4 = new CustomerThread("顧客4",store);
             //啓動四個線程
             ct1.start();
             ct2.start();
             ct3.start();
             ct4.start();
       }
} 
//商店類
class Store {
       //剩餘商品數量
       static int goodsNumble = 3;
       public Store(){
       }
       //出售一件商品
 void sell() {
        //先判斷商品是否售完
             if(goodsNumble<=0)
                    System.out.println("商店:出售失敗,商品賣完了");
             else {
                    System.out.println("此時還有"+goodsNumble+"件商品");
                    goodsNumble--;
                    System.out.println("售出一件商品,商品數量減1");
                    }
       }
}
class CustomerThread extends Thread{
       private Store store;
       public CustomerThread(String name,Store store) {
             // TODO 自動生成的構造函數存根
             setName(name);
             this.store = store;
       }
       @Override
       public void run() {
             buy();
       }
       //購買商品,調用store的sell方法
       public void buy() {
             store.sell();
       }
}

運行結果
在這裏插入圖片描述

從上面的代碼和運行結果我們可以看到,因爲顧客幾乎是同時到達商店的,所以他們到的時候商品的水量都是3,於是商店給每一位顧客都出售了一件商品。這顯然不是我們想要的結果。然後我們給Store類的sell()方法加上synchronized關鍵字,局部修改如下:

 synchronized void sell() {
        //先判斷商品是否售完
             if(goodsNumble<=0)
                    System.out.println("商店:出售失敗,商品賣完了");
             else {
                    System.out.println("此時還有"+goodsNumble+"件商品");
                    goodsNumble--;
                    System.out.println("售出一件商品,商品數量減1");
                    }
       }

運行結果
在這裏插入圖片描述

可以看到在我們給sell方法使用了synchronized關鍵字後Stroe會按照一定的順序來爲顧客服務,這就是synchronized方法的效果.噹噹前線程使用synchronized方法時,會給當前對象(store)上鎖,上鎖後其他線程在使用相同對象的方法時進入阻塞,需等待此線程中的該方法結束,才能繼續運行。

線程同步的三個方法

爲了介紹線程同步的三個方法,我們先來看一下這樣一個場景:有一個房間,房間裏居住着四個人,四個人共用着房間裏的一部電話。每個人都可以在任何時候使用電話,但是如果他們在同一時間都來使用這部電話又會發生什麼呢?
在這裏插入圖片描述
我們用代碼模擬一下:

public class SynchronizedMethod {
       public static void main(String[] args) {
             Room r = new Room();
             //創建並啓動4個學生線程
             StudentThread s1  =new StudentThread("s1",r);
             StudentThread s2  =new StudentThread("s2",r);
             StudentThread s3  =new StudentThread("s3",r);
             StudentThread s4  =new StudentThread("s4",r);
             s1.start();
             s2.start();
             s3.start();
             s4.start();
       }
}

class Room {
       public Phone phone;
       public Room() {
             // TODO 自動生成的構造函數存根
             phone = new Phone();
       }       
}

class Phone{
       static final int FREE = 1,BUSY = 0;
       //電話的狀態
       private int status=FREE;
       public Phone() {
       
       }
       //判斷電話是否空閒
       public boolean  isFree() {
             return status==FREE;
       }
       //打電話,半秒後掛斷,爲了打印電話使用者的名字,我們傳入一個參數
       public void call(String user) {
             if(!isFree()) {
                    System.out.println("有人正在使用電話哦");
                    return;
             }
             status = BUSY;
             System.out.println(user+"打出了電話...");
             try {
                    Thread.sleep(500);
                    hangUp(user);
             } catch (InterruptedException e) {
                    // TODO 自動生成的 catch 塊
                    e.printStackTrace();
             }
       }
       //掛電話
       public  void hangUp(String user) {
             status = Phone.FREE;
             System.out.println(user+"掛斷了電話...");
       }
}

class StudentThread extends Thread{
       private Room room;
       public StudentThread(String name,Room room) {
             setName(name);
             this.room = room;
       }
       @Override
       public void run() {
                    this.room.phone.call(getName());
       }
}

運行結果
在這裏插入圖片描述

我們可以發現當線程s1使用了對象phonecall方法的時候,其他三個線程仍然使用了call方法。這就是線程不同步的效果。接下來我們分別用三種方法實現線程同步。

  • synchronized方法
    Phone類的call聲明爲sychronized方法(在call方法前面加上關鍵字即可),修改部分代碼如下:
       public synchronized void call(String user) {
             if(!isFree()) {
                    System.out.println("有人正在使用電話哦");
                    return;
             }
             status = BUSY;
             System.out.println(user+"打出了電話...");
             try {
                    Thread.sleep(500);
                    hangUp(user);
             } catch (InterruptedException e) {
                    // TODO 自動生成的 catch 塊
                    e.printStackTrace();
             }
       }

運行結果
在這裏插入圖片描述

  • synchronized
    call函數內添加synchronized代碼塊,此方法與上一方法相比,優點在於你不需將整個方法都變成同步的,只需把關鍵代碼塊變爲同步即可,修改部分代碼如下:
public  void call(String user) {
             synchronized (this) {
                    if(!isFree()) {
                           System.out.println("有人正在使用電話哦");
                           return;
                    }
                    status = BUSY;
                    System.out.println(user+"打出了電話...");
                    try {
                           Thread.sleep(500);
                           hangUp(user);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
             }
       }

運行結果
在這裏插入圖片描述

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

class Phone{
       private Lock lock= new ReentrantLock();
       static final int FREE = 1,BUSY = 0;
       //電話的狀態
       private int status=FREE;
       public Phone() {
             
       }
       //判斷電話是否空閒
       public boolean  isFree() {
             return status==FREE;
       }
       //打電話,半秒後掛斷,爲了打印電話使用者的名字,我們傳入一個參數
       public  void call(String user) {
             //上鎖
             lock.lock();
                    if(!isFree()) {
                           System.out.println("有人正在使用電話哦");
                           return;
                    }
                    status = BUSY;
                    System.out.println(user+"打出了電話...");
                    try {
                           Thread.sleep(500);
                           hangUp(user);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
                    //釋放鎖
                    lock.unlock();
       }
       //掛電話
       public  void hangUp(String user) {
             status = Phone.FREE;
             System.out.println(user+"掛斷了電話...");
       }
}

運行結果
在這裏插入圖片描述

線程死鎖

線程死鎖,是指多個線程在使用公共資源時各佔了資源的一部分,導致每個線程都等待其他線程釋放資源才能完成工作,最終導致每個線程都無法完成工作,互相卡死。
想象一下這樣的情景:上學時,你和你的同桌共用一個筆記本和一支鋼筆,只是你的老師講到了一個要點,於是你順勢搶到了鋼筆,而你的同桌拿到了筆記本,誰也不肯讓步,於是誰也無法完成筆記。
我們用代碼模擬一下:

public class DeadLock {
       public static void main(String[] args) {
             Stationery stationery = new Stationery();
             //p1 takeNotes時先獲取並鎖定本子,然後再試圖獲取筆
             Person p1 = new Person("p1",stationery) {
                    @Override
                    public void takeNotes() {
                           synchronized (stationery.notebook) {
                                 getNotebook();
                                 synchronized (stationery.pen) {
                                        getPen();
                                 }
                           }
                    }
             };

             //p2 takeNotes時先獲取並鎖定筆,然後再試圖獲取本子
             Person p2 = new Person("p2",stationery) {
                    @Override
                    public void takeNotes() {
                           synchronized (stationery.pen) {
                                 getPen();
                                 synchronized (stationery.notebook) {
                                        getNotebook();
                                 }
                           }
                    }
             };
             p1.start();
             p2.start();
       }
}

//文具類(共用資源)
class Stationery{
       //筆
       public static Pen pen = new Pen();
       //筆記本
       public static Notebook notebook= new Notebook();
}

class Pen{
       
}

class Notebook{

}
abstract class Person extends Thread{
       Stationery stationery;
       public Person(String name,Stationery s) {
             // TODO 自動生成的構造函數存根
             setName(name);
             stationery = s;
       }
       //獲取筆
       Pen getPen() {
                    System.out.println(getName()+":我拿打筆了");
                    try {
                           Thread.sleep(1);
                    } catch (InterruptedException e) {
                           // TODO 自動生成的 catch 塊
                           e.printStackTrace();
                    }
             return stationery.pen;
       };
       //獲取本子
       Notebook getNotebook() {
                    System.out.println(getName()+":我拿打筆記本了");
                    try {
                           Thread.sleep(1);
                    } catch (InterruptedException e) {
                           // TODO 自動生成的 catch 塊
                           e.printStackTrace();
                    }
             return stationery.notebook;
       };
       //抽象方法 
       public abstract void takeNotes();
       @Override
       public  void run() {
             takeNotes();
       };
}

運行結果(程序並沒有結束而是一直卡住無法往下運行)
在這裏插入圖片描述

線程的生命週期

在這裏插入圖片描述

  • 新建態 :Thread t = new Thread(),線程對象被創建,此時還沒有使用start()方法,啓動線程
  • 就緒狀態 :t.start()後進入就緒狀態,但具體什麼時候運行取決於CPU的調度
  • 運行態:線程佔用CPU運行
  • 阻塞態:線程在運行時,使用了Thread.sleep()或者進行I/O操作等不在佔用CPU,則由運行態進入阻塞態,待事件結束再進入就緒態
  • t.stop()線程結束,進入終止態(消亡狀態)

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