Java:基礎知識——數據類型、關鍵字、集合、IO流、類、線程(生產者與消費者)、模式

一、數據類型

??1、 四類八種:
取值范圍小的可向大的轉,大轉小會失去精度

類型 名稱 占用字節 取值范圍 默認值 包裝類
整形 byte 字節型 1(8 Bit) -2^7 ~ 2^7-1 0 Byte
short 短整型 2(16 Bit) -2^15 ~ 2^15-1 0 Short
int 整形 4(32 Bit) -2^31 ~ 2^31 - 1 0 Integer
long 長整型 8(64 Bit) -2^63 ~ 2^63-1 0L Long
浮點型 float 浮點型 4(32 Bit) (7位有效數字) 0.0f Float
double 雙精度浮點型 8(64 Bit) (16位有效數字) 0.0d Double
字符型 char 字符型 2(16 Bit) \u0000 ~ \uFFFF \u0000 Character
布爾型 boolean 布爾型 1/8(1 Bit) false、true false Boolean

??2、String 類:字符串,由英文雙引號括起來的由0到多個數字、字母、字符共同組成的一個串,可賦值為null。而char是由英文單引號括起來的1個數字或字符或字母。


二、訪問權限修飾符、通配符

??1、public:類內部、同個package、子類、任何地方
??2、protected:類內部、同個package、子類。即可以被子類和同一個包內的類訪問。
??3、default:類內部、同個package。即只可以被同一個包內的類訪問。
??4、peivate:類內部。即只可以被該類內部自己訪問。
??5、通配符:使用問號 " ? " 表示所有類型

class Test {
      public void showInfo(List<?> list) {
         
      }
}

??6、有限制的通配符:例如泛型為<? extends Person>,表示只允許泛型類型為Person或Person子類的引用調用


三、關鍵字this、super、static、final、abstract

??1、this:表示引用本類對象的屬性、方法。先從自己的類中引用,若找不到,如果有父類,則會從父類里找。

??2、super:子類用super來調用父類的屬性、方法或構造器,可用于區分子父類之間同名的成員。super可以一直向上追溯多個層級的父類。

??3、static:類變量(靜態變量),不用通過實例化就能調用,直接 類名.該關鍵字變量名(Person.country),被所有這個類的實例化對象所共享。可修飾屬性、方法、代碼塊和內部類。

public class Person {
    static Stirng country;
    int age;
    Stirng name;

    public static void getInfo() {
       ······
    }
}

??4、final:標記的變量為常量,只能被賦值一次,名稱大寫。標記的類不能被繼承,標記的方法不能被重寫

??5、abstract:修飾類或方法,成為抽象類或抽象方法

四、集合、迭代、泛型

??1、Map:具有映射關系的集合,保存具有映射關系的數據,即 key-value對 一對一
????????1-①:HashMap是Map的接口的典型實現

Map map = new HashMap();

????????1-②:TreeMap對所有的 key-value對 處于有序狀態,默認為自然排序

??2、List:有序,可重復的集合
????????2-①:ArrayList是List的接口的典型實現

List list= new ArrayList();

??3、Set:無序、不可重復的集合
????????3-①:HashSet是Set接口的典型實現,存放值時根據值的hashcode值來決定存放位置。集合中的元素可以為null。

Set set = new HashSet();

????????3-②:TreeSet是SortSet接口的實現類,它會調用集合元素的compareTo(Object obj)方法,確保集合元素處于排序狀態。必須放入同樣類的對象。默認為自然排序,升序排列。

Set set = new TreeSet();

??4、foreach 迭代集合:使用格式為

for(Object obj : 要迭代的對象) {
      System.out.print(obj);
}

??5、Iterator 迭代器遍歷集合:使用格式為

Iterator<Integer> it = set.iterator();
while(it.hashNext()) {
       System.out.print(it.next());
}

??6、foreach與Iterator的區別:Iterator 提供可在遍歷時刪除集合內元素的方法。

??7、泛型<類/接口>:讓集合只能存放相同類型的對象/類(若是數據類型用Integer、String之類的)。

Set<String> set = new HashSett<String>();

interface Generator<T> {
      T test(T t);
}

五、IO(字節流xxxStream與字符流xxxReader/xxxWriter)

flish():關于 flush() 的含義,清空緩沖區。

??1、File類:能新建、刪除和重命名文件和目錄。(如需要訪問文件內容則需要輸入/輸出流)
??2、Input:計算機把外部數據(磁盤、光盤等儲存設備)讀到自己的代碼程序中為“輸入”。(計算機把外部數據輸入到自己的程序中)

??3、Output:計算機把代碼程序中的數據寫到磁盤、光盤等儲存設備中為“輸出”。(計算機把程序的數據輸出到磁盤中)

??4、文件流(數據流的讀寫是基于文件的操作)計算機與硬盤之間的io操作,讀寫較慢
????????4-①:FileInputStream 字節輸入流

public static void main(String[] args) {
    try {
          FileInputStream in = new FileInputStream("文件位置");  
          // 把文件字節輸入流放到緩沖字節輸入流對象里
          // BufferedInputStream bis = new BufferedInputStream(in);

          // 讀取的是字節
          byte[] b = new byte[100];
          in.read(b);
          // 從磁盤中讀取xx個字節的數據進入到程序中
          System.out.println(new String(b));

          // 最晚開的最早關
          // bis.cloce(); 
          in.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

????????4-②:FileOutputStream 字節輸出流

public static void main(String[] args) {
    try {
          // 后面 true 為追加模式(append),避免直接覆蓋
          FileOutputStream out = new FileOutputStream("文件位置", true);
          // 把文件字節輸入流放到緩沖字節輸入流對象里
          // BufferedOutputStream bos = new BufferedOutputStream(out);
          String str = "一段文本";
  
          // 從程序中輸出字節到磁盤中保存
          out.write(str.getBytes());
          out.flush();
          // 最晚開的最早關
          // bos.cloce(); 
          in.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

????????4-③:FileReader 字符輸入流

public static void main(String[] args) {
    try {
          FileReader fr = new FileReader("文件位置");
          // 把文件字節輸入流放到緩沖字節輸入流對象里
          // BufferedReader br= new BufferedReader (fr);

          // 讀取的是字符
          char[] c = new char[45];
          fr.read(c);
          System.out.println(new String(c));
          // 最晚開的最早關
          // br.cloce();
          fr.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

????????4-④:FileWriter 字符輸出流

public static void main(String[] args) {
    try {
          FileWriter fw = new FileWriter("文件位置", true);
          // 把文件字節輸入流放到緩沖字節輸入流對象里
          // BufferedWriter bw= new BufferedWriter(fw);
          String str = "一段文本";

          fw.write(str);
          fw.flush();
          // 最晚開的最早關
          // bw.cloce();
          fw.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

??5、緩沖流(數據流的讀寫是基于內存的操作)把數據先緩沖進內存里,能提高數據的讀寫速度
????????5-①BufferedInputStream
????????5-②BufferedOutputStream
????????5-③BufferedReader
????????5-④BufferedWriter

??6、轉換流(字節流、字符流之間轉換)
????????6-①InputStreamReader:字節輸入流轉換成字符輸入流
例:

FileInputStream fis = new FileInputStream("文件位置");
InputStreamReader isr = new InputStreamReader(fis, "編碼格式");

????????6-②OutputStreamWriter:字節輸出流轉換成字符輸出流

FileOutputStream fos = new FileOutputStream("文件位置");
OutputStreamWriter isr = new OutputStreamWriter(fos, "編碼格式");

??7、隨機存取流
????????7-①:RandomAccessFile類:程序可隨機跳到文件的任意地方來讀寫文件

// "r":只讀
RandomAccessFile ra = new RandomAccessFile("文件位置", "r");
// 文件起始點從零開始
ra.seek(0);

// 設置讀取字符串的長度
bytep[] b = new byte[100];
System.out.print(new String(b));

??8、對象流
????????8-① 序列化 Serialize 將對象轉化成字節流儲存在硬盤中。用 ObjectInputStream類將一個Java對象(該java對象必須實現Serializable接口)寫入IO流中,凡是實現 Serializable 接口的類,都有一個序列化版本標識符的靜態變量。若要序列化和反序列化同一個對象,則要保證serialVersionUID是相同的
實現序列化接口的對象 Person.java

import java.io.Serializable;

public class Person implements Serializable{
    // serialVersionUID  用來表明類的不同版本,若不自定義則會自動隨機生成
    private static final long serialVersionUID = 1L;
    int age;
    String gender;
    String name ;

    public Person() {}
    public Person(int age, String gender, String name) {
        this.age = age;
        this.gender = gender;
        this.name = name;
    }

    public int getAge() {
        return age;
    }
    public String getGender() {
        return gender;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public void setName(String name) {
        this.name = name;
    }
}

序列化

public static void main(String[] args) {
    Person p = new Person(25, "男", "張三");
    try{
        // 用對象流將java對象轉換為字節流,并輸出到指定文件位置
        FileOutputStream fos = new FileOutputStream("文件位置");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        // 對象流將對象寫入文件中
        oos.writeObject(p);
        // System.out.println(p.getName());
        oos.flush();
        oos.close();
    }catch(Exception e){
        e.getStackTrace();
    }
 }

????????8-② 反序列化 Deserialize將字節流轉化成對象。用ObjectOutputStream類從IO流中恢復該Java對象。
反序列化

public static void main(String[] args) {
    try{
        // 計算機讀取(輸入)文件,用對象流將字節流轉換為java對象,并保存到對應類型(強制轉換)的類中
        FileInputStream fis = new FileInputStream("文件位置");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Person p = (Person)ois.readObject();
        ois.close();
        System.out.println("反序列化:" + p.getName());
    }catch(Exception e){
        e.getStackTrace();
    }
 }

六、類

??1、方法重載(同一個類里)
????????方法名相同,但參數個數不同、參數的數據類型不同或者返回值的數據類型不同。

??2、方法重寫(子類對父類方法的覆蓋)
????????子類對父類的方法重寫,只能重寫方法體的代碼(不能使用更嚴格的訪問權限、拋出的異常不能大于父類被重寫方法的異常、同為static或!static)。

??3、初始化塊{······}(無static修飾)
????????執行順序:靜態代碼塊(只執行一次)-->初始化塊(每次實例化一個對象都執行一次)-->構造方法,按順序執行多個初始化塊。

??4、抽象類 abstract
????????特征通用的父類,不能被實例化,里面的方法必須被子類重寫,方法體由子類提供,且重寫方法時方法上面需添加 @override

??5、接口 interface
????????一個類里可實現多個接口,接口可繼承其他接口,實現接口類用 class xxx implements 接口類名{}。接口中所有的成員變量默認都是public static final修飾的,所有方法默認都是public abstract修飾的(這些修飾符可忽略不寫),接口沒有構造器。

抽象類與接口的區別:抽象是對一類事物的高度抽象,包括屬性和方法,形成一個父類;接口是對一系列動作的抽象,實現相應的接口即為完成一系列動作。

??6、枚舉類 enum

public enum Season{
      SPRING("春天","百花");
      SUMMER("夏天","熱烈");
      AUTUMN("秋天","蕭瑟");
      WINTER("冬天","埋葬");

      private final String name;
      private final String desc;

      prinvate Season(Strinf name, String desc) {
        this.name = name;
        this.desc = desc;
      }

      public void showInfo() {
            System.out.print(this.name + ":" + this.desc);
      }
}
public class Test {
      public static void main(String[] args) {
            Season spring = Season.SPRING;
            speing.showInfo();
      }
}

??7、自定義工具類Utils.java
????????里面放入許多個需要反復執行的方法。

??8、Collections操作集合的工具類
????????是一個操作List、Map、Set等集合的工具類。此類提供了大量方法對集合元素進行排序、查詢和修改等操作。也可解決多線程并發訪問集合時的線程安全等問題。

??9、Javabean:是一個包裝好的可以重復使用的組件,一種特殊的Java類——public 類、有一個無參的公共構造器、private 屬性、且有對應的 public get set方法用來修改和獲取屬性、(也可以有一系列的操作方法和事件處理器)。

七、線程

生命周期:新建(執行start()方法之前,線程實例創建,尚未被啟動)、就緒(執行start()方法之后,在就緒隊列中排隊)、運行(獲得CPU資源正在執行run()方法)、阻塞(正在運行的線程讓出CPU并暫停自己的執行run()方法,可通過wait()進入等待狀態、或通過在獲取同步鎖失敗后進入等待狀態、或通過sleep()或join()使線程進入阻塞狀態,阻塞狀態終止之后會轉回就緒狀態重新排隊)、死亡(自然終止或用stop()方法終止)
??1、程序:某種語言編寫的一組指令的集合。
??2、進程:是程序的實體,一個程序至少有一個進程,存在它自身的產生、存在和消亡。
??3、線程:是進程的一個實體,一個線程只能屬于一個進程,而一個進程至少有一個線程,線程是程序內部的一條執行路徑。(若程序可同一時間執行多個線程,則這個程序就是支持多線程的)
??4、同步與異步:同步是指一個進程在執行某個請求的時,若該請求需要一段時間才能返回信息,那么這個進程會一直等待,直到接收了返回信息才能繼續執行下去;異步是指進程不需要一直等待,在接受返回信息的期間,可以繼續執行下面的操作,不管其他進程的狀態。
??5、創建線程的兩種方式
????????5-①繼承Thread類:自定義子類繼承→子類重寫 run() →創建Thread子類對象→子類對象調用線程 strat()
執行多次后可發現線程是異步進行的(main線程與ThreadSon線程)

public class Test {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName();
        Thread thread = new ThreadSon();
        thread.start();
        System.out.println(name + " 線程:4");
        System.out.println(name + " 線程:5");
        System.out.println(name + " 線程:6");
    }
}

class ThreadSon extends Thread{
    @Override
    public void run() {
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
    }
}

輸出結果為:

>> main 線程:4
>> 1
>> main 線程:5
>> 2
>> 3
>> main 線程:6

????????5-②實現 Runnable 接口

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new toSleepRunnble());
        // 或者使用如下方式:
        // Runnable run = new toSleepRunnble();
        // Thread thread = new Thread(run);

        thread.start();
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
    }
}

class toSleepRunnble implements Runnable{
    @Override
    public void run() {
        System.out.println("1");
        System.out.println("2");
    }
}

??5、相關方法
????????5-① yield() 線程讓步:讓其它優先級相同或更高且數據量更大的線程優先獲得運行機會,此時調用此方法的線程回到就緒狀態重新排隊(效果有待商榷)
????????5-② join():即使是低優先級的線程也可以獲得先執行權。即阻塞當前所在類的活動線程,讓調用了 .join() 方法的線程插入進來活動,等到join進來的線程執行完畢,再讓main方法執行其它被阻塞的線程。

關于join()的使用實例

public class Test {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName();
        Thread thread = new ThreadSon();
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + " 線程:4");
        System.out.println(name + " 線程:5");
        System.out.println(name + " 線程:6");
    }
}

class ThreadSon extends Thread{
    @Override
    public void run() {
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
    }
}

輸出結果為:

>> 1
>> 2
>> 3
>> main 線程:4
>> main 線程:5
>> main 線程:6

若不添加 join() 則:

>> 1
>> main 線程:4
>> main 線程:5
>> 2
>> main 線程:6
>> 3

????????5-③ sleep(long millis):讓當前活動線程“沉睡”指定的時間,而其他線程先執行,但不會釋放占用的同步鎖,時間到后釋放鎖,回到就緒狀態重新排隊。
????????5-④ wait():令當前線程掛起并放棄CPU、同步資源(同步鎖),使別的線程可訪問并修改共享資源,而當前線程排隊等候再次對資源的訪問。需要使用notify()來喚醒。

關于sleep() 與wait()的區別: wait()(或wait(long))是Object的一個方法,且需放在同步鎖的方法中,且須使用notify()(或timing out)才能喚醒該線程占有的同步鎖;sleep()是Thread的一個方法,若該線程在使用了sleep()的前面又使用了同步鎖,則需要等線程主動醒來才能退出阻塞狀態且釋放這個鎖。

????????5-⑤ notify()喚醒正在排隊等候同步資源的線程中優先級最高者結束等待。

關于wait() 與 notify() 的簡析

????????5-⑥ stop():強制線程生命期結束
????????5-⑦ boolean isAlive():判斷線程是否還活著

關于 wait() 和 notify() 的使用實例——消費者與生產者問題
題目:生產者(Producer)將產品交給店員(Clerk,相當于倉庫),消費者(Customer)從店員處取走產品。店員一次只能拿 20 個產品(生產者最大生產量),如果生產者試圖生產更多產品,店員會叫生產者停一下,等待下次通知才能生產。當店員拿來產品,店員會通知消費者前來消費。如果店中的產品數量不足,店員會叫消費者等一下,等待下次店員拿來了產品后通知了消費者,消費者才能消費。

一個生產者線程與一個消費者線程

public class TestThread {
    public static void main(String[] args) {
        /**
         * 多個線程排隊獲取CPU的使用權
         * 情況一:當其中一個線程獲取了同步鎖,并使用了 wait() 方法后,這個線程從運行狀態退出,
         * 進入等待阻塞狀態(進入等待隊列),同時釋放所持有的同步鎖(先獲取鎖再釋放鎖)
         * 直到被另一個線程使用 notify() 喚醒,其它的線程才從阻塞狀態進入到可運行狀態。(前提是這幾個個線程等待的是同一共享資源(同一個同步鎖))
         * 情況二:當其中一個線程獲取了鎖,根據判斷條件若不使用 wait() 方法,那么將直接執行 wait() 下面的代碼
         * 等這些步驟執行完,就可以使用 notify()或notifyAll() 方法喚醒(通知)其它線程去排隊獲取同步鎖
         */
        Clerk clerk = new Clerk();  //店員

        Thread producThread = new Thread(new Producer(clerk), "生產者");
        Thread customerThread = new Thread(new Customer(clerk), "消費者");

        producThread.start();
        customerThread.start();
    }
}

//店員
class Clerk{
    // public int totalProduct = 0;    //店員身上有 0 個產品
    // public boolean isEnough = false;    //產品不足
    public int totalProduct = 20;    //店員身上有 20 個產品,初始化時預設店員身上產品充足
    public boolean isEnough = true;    //產品是否足夠,false為庫存不足,true為庫存足夠
    public static final int MAX_PRODUCT = 20;   //店員身上最大的產品數為20
}


//生產者
class Producer implements Runnable{
    Clerk clerk;
    String name;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        /**
         * 注意使用 while 在外圍包含著同步鎖的原因:為了讓生產者無線次數地生產產品,消費者不斷地消費產品。
         * 若去掉外面的 while 循環的話,生產者線程只能被 notify 一次,即只能執行一次 wait() 后面的操作,造成生產者線程只能執行一次 run() 
         * 當生產者線程再次獲取cpu使用權時,因為run方法已經執行完畢,就會自動 exit() 結束線程。徒留消費者線程一直在 wait 中。
         */
        while(true){
            synchronized(clerk){
                try {
                    //獲取當前線程的名稱
                    name = Thread.currentThread().getName();
                    
                    /**
                     * 當判斷產品足夠時,執行wait()方法,同步鎖被釋放,生產者線程進入等待阻塞隊列,不執行后面的操作
                     * 關于wait()方法放在while循環里是為了防止線程被錯誤地、提早地被喚醒
                     */
                    while(clerk.isEnough){
                        System.out.println( name + " 拿到了同步鎖,發現貨物還足夠,于是繼續等待···");
                        clerk.wait();
                        System.out.println( name + " 再次拿到了同步鎖,從 wait() 中被喚醒!");
                    }

                    //需要操作的代碼塊放在 wait()下面是因為,當線程被 notify 之后是從 wait() 下面的代碼塊繼續執行的
                    System.out.println(name + " 正在生產產品···");
                    while(clerk.totalProduct < clerk.MAX_PRODUCT){
                        //生產者每隔 3 秒生產 5 個產品
                        Thread.sleep(3000);
                        if(clerk.MAX_PRODUCT - clerk.totalProduct >= 5){
                            clerk.totalProduct += 5;
                            System.out.println("---->已經生產 5 個產品,現在產品庫存為: " + clerk.totalProduct + " 個產品!");
                        }
                        else{
                            int num = clerk.MAX_PRODUCT-clerk.totalProduct;
                            clerk.totalProduct += num;
                            System.out.println("---->已經生產 " + num + " 個產品,現在產品庫存為: " + clerk.totalProduct + " 個產品!");
                            Thread.sleep(1000);
                        }
                    }
                    System.out.println("生產完畢,產品數量已充足,請消費者繼續購物~~~");
                    clerk.isEnough =true;
                    Thread.sleep(3000);
                    
                    //因為此時消費者線程在等待阻塞狀態,所以使用 notify() 時,在等待狀態的線程收到通知后退出等待隊列
                    //多個消費者線程時使用 clerk.notifyAll();
                    clerk.notify();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
       }
    }
}


//消費者
class Customer implements Runnable{
    Clerk clerk;
    String name;
    int num;

    public Customer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        //不斷有消費者來購買產品
        while(true){
            synchronized(clerk){
                try {
                    name = Thread.currentThread().getName();

                    //當產品不足時等待,且釋放同步鎖,讓其它線程有機會獲取同步鎖
                    while(clerk.isEnough == false){
                        System.out.println( name + " 拿到了同步鎖,發現貨架上產品不夠了,于是繼續等待···");
                        clerk.wait();
                        System.out.println( name + " 再次拿到了同步鎖,從 wait() 中被喚醒!");
                    }
                    
                    //消費者每次執行一次購買產品的操作
                    System.out.println( name + " 進來了,正在消費···");
                    //消費者隨機購買[1,8]個產品數(即最多買 8 個產品,最少買 1 個)
                    num = (int)(Math.random() * 8) + 1;
                    if(clerk.totalProduct >= num){
                        //消費者每隔 2 秒消費 num 個產品
                        Thread.sleep(2000);
                        clerk.totalProduct -= num;
                        System.out.println("----> " + name + " 購買了 " + num + " 個產品,現貨柜剩余: " + clerk.totalProduct + " 個產品!");
                        Thread.sleep(1000);
                        System.out.println(name + " 離開了!");
                        Thread.sleep(1000);
                        System.out.println("請下一位消費者上前購物~~~");
                    }else{
                        Thread.sleep(2000);
                        System.out.println(name + " 需要購買 " + num + " 個產品,看到貨柜產品數量不太夠自己的需求而離開了!店員正在提醒生產者生產產品~~~");
                        clerk.isEnough = false;
                    }
                    Thread.sleep(3000);
                    //因為此時生產者線程在排隊等待的狀態,所以使用 notify() 時,在等待狀態的線程 收到通知后退出等待隊列
                    //如果有多個線程則 clerk.notifyAll();
                    clerk.notify();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

結果如下:

一個生產者一個店員一個消費者

一個生產者線程與多個消費者線程

public class TestThread {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();  //店員

        Thread producThread = new Thread(new Producer(clerk), "生產者");
        Thread customerThread = new Thread(new Customer(clerk), "阿大");
        Thread customerThread2 = new Thread(new Customer(clerk), "小二");
        Thread customerThread3 = new Thread(new Customer(clerk), "張三");
        Thread customerThread4 = new Thread(new Customer(clerk), "李四");
        Thread customerThread5 = new Thread(new Customer(clerk), "王五");

        producThread.start();
        customerThread.start();
        customerThread2.start();
        customerThread3.start();
        customerThread4.start();
        customerThread5.start();
    }
}

同時把 notify() 替換為 notifyAll();

結果如下:

一個生產者多個消費者

需要注意的問題如下:

一、wait() 方法在 while 里面而不在 if 里面的原因:可能會出現某一線程被 提前喚醒 通知去獲取同步鎖(多線程的情況下)
????若 wait() 在 if 語句if(condition){ obj.wait(); }
????那么線程執行 run 方法時可能會出現以下這種情況:

A線程按順序執行 if 語句→ 若 condition == trueA線程執行 wait()方法 放棄同步鎖,進入等待阻塞隊列,此時A線程的 run() 方法尚未執行完 → 另一個在排隊的B線程搶到同步鎖,獲取同步鎖的B線程執行完一系列操作,然后執行 notifyAll()方法 喚醒其它線程,B線程run()方法 執行完畢C線程被喚醒,并且搶到同步鎖的C線程執行完一系列操作,然后執行 notifyAll()方法 喚醒其它線程 → ······ → 直到A線程從 wait() 中被其它線程的 notify() 喚醒,搶到同步鎖的 A線程 不再從最開始的 if語句 開始判斷,而是直接執行 wait() 后面的語句——執行完 if 語句 范圍內的 wait() 后面的語句,再執行 if 語句 范圍外的 wait() 下方的語句(在這一步中,如果某些條件已經被改變了而A線程又不做判斷的話,很容易導致出錯!),執行完一系列操作后接著執行 notifyAll() 方法 喚醒其它線程,此時A線程的 run() 方法執行完畢(其它線程被喚醒并執行一系列操作······)A線程在排隊時再次搶到同步鎖,然后按順序執行 if 語句······

????若 wait() 在 while 語句while(condition){ obj.wait(); }
????那么線程執行 run 方法時會出現以下這種情況:

A線程按順序執行 while 語句→ 若 condition == trueA線程執行 wait()方法 放棄同步鎖,進入等待阻塞隊列,此時A線程的 run() 方法尚未執行完 → 另一個在排隊的B線程搶到同步鎖,獲取同步鎖的B線程執行完一系列操作,然后執行 notifyAll()方法 喚醒其它線程,B線程run()方法 執行完畢C線程被喚醒,并且搶到同步鎖的C線程執行完一系列操作,然后執行 notifyAll()方法 喚醒其它線程 → ······ → 直到A線程從 wait() 中被其它線程的 notify() 喚醒,搶到同步鎖的 A線程 不再從最開始的 while 語句 開始判斷,而是直接執行 wait() 后面的語句——執行完 while 語句 范圍內的 wait() 后面的語句,再因為 while 循環 而回到之前再次判斷 condition是否為 true ,若 condition == true 則A線程放棄同步鎖,繼續等待;若 condition == false ,則A線程跳出 while 循環,執行 while 語句 范圍之外的 wait() 下方的語句,執行完一系列操作后接著執行 notifyAll() 方法 喚醒其它線程,此時A線程的 run() 方法執行完畢(其它線程被喚醒并執行一系列操作······)A線程在排隊時再次搶到同步鎖,然后按順序執行 where 語句······

二、synchronized() 鎖外面被 while(true) 包含:為了將生產者這個線程不斷生產和消費者這個線程不斷消費,所以要在鎖外面用 while(true) 設置死循環(根據情況,也可以將 true 改為其他限制條件)

??6、同步鎖 synchronized:一個線程訪問一個對象中的synchronized同步代碼塊時,其他試圖訪問該對象的線程將被阻塞
模擬用戶的取票操作(用戶先來的先取票,后來的后取票)
????????6-①:在方法聲明中加上鎖,表示這個方法為同步方法

public class Test {
    public static void main(String[] args) {
        TicketMachine tm = new TicketMachine();
        
        // 新建線程且自定義線程名稱
        Runnable user1_run = new UserRunnable(tm, 47);
        Thread user1 = new Thread(user1_run, "第一個用戶");

        Runnable user2_run = new UserRunnable(tm, 5);
        Thread user2 = new Thread(user2_run, "第二個用戶");
        
        user1.start();
        user2.start();
        
    }
}

// 取票機器
class TicketMachine{
    int total = 50;
    String threadName;

    public synchronized void getTicket(int n){
       threadName = Thread.currentThread().getName();
        if(total >= n){
            System.out.println(threadName +" 操作中,原有" + total + "張票!");
            total -= n;
            System.out.println(threadName +" 現在已經取了" + n + "張票了!,還剩" + total + "張票!!");
        }else{
            System.out.println("票已經不足,請前往另一臺機器取。");
        }
    }

}

class UserRunnable implements Runnable{
    TicketMachine tm;
    int n;
    
    // 帶參數的構造方法
    public UserRunnable(TicketMachine tm, int n) {
        this.tm = tm;
        this.n = n;
    }

    @Override
    public void run() {
        // 用戶從機器中取了n張票
        tm.getTicket(n);
    }
}

不加同步鎖

加了同步鎖


?????????????或

????????6-②:在方法中加上對象鎖(鎖住同一個“取票機器”這一對象)

public void getTicket2(int n, TicketMachine tm){
        synchronized(tm){
            name = Thread.currentThread().getName();
            if(total >= n){
                System.out.println(name+"操作中,原有" + total + "張票!");
                total -= n;
                System.out.println(name+"現在已經取了" + n + "張票了!,還剩" + total + "張票!!");
            }else{
                System.out.println("票已經不足,請"+name+"前往另一臺機器取。");
            }
        }
    }
    // 相應main方法里的傳參變化
    public static void main(String[] args) {
        TicketMachine tm = new TicketMachine();
        
        // 新建線程且自定義線程名稱
        Runnable user1_run = new UserRunnable(47, tm);
        Thread user1 = new Thread(user1_run, "第一個用戶");

        Runnable user2_run = new UserRunnable(5, tm);
        Thread user2 = new Thread(user2_run, "第二個用戶");
        
        user1.start();
        user2.start();
        
    }

八、模式

??1、單例設計模式
????????若實例化一個對象要占用大量的時間和資源,則只實例化一個對象。

??2、模板方法設計模式
????????抽象類(父類)作為多個子類的通用模板,子類在抽象類的基礎上擴展、改造。

??3、工廠方法
????????通過工廠把new對象隔離,通過接口來接受不同產品的實現類。
例:面包的生產
Bread接口以及Bread類

// 面包接口
public interface Bread {
    // 面包的原材料
    void showMaterialsInfo();
    // 面包的口味介紹
    void showTasteInfo();
}

// 奶油面包
class CreamBread implements Bread{
    @Override
    public void showMaterialsInfo() {
        System.out.println("奶油面包的原材料:牛奶、黃油、干酵粉、砂糖,鹽,和雞蛋。");
    }

    @Override
    public void showTasteInfo() {
        System.out.println("奶油香甜順滑,面包松軟。");
    }
    
}

// 芝士面包
class CheeseBread implements Bread{
    @Override
    public void showMaterialsInfo() {
        System.out.println("芝士面包的原材料:高粉、低粉、雞蛋液、水、黃油、白糖、鹽、酵母。");
    }

    @Override
    public void showTasteInfo() {
        System.out.println("剛出爐的芝士面包可拉絲,口感豐富、營養美味。");
    }
    
}

// 法棍
class Baguette implements Bread{
    @Override
    public void showMaterialsInfo() {
        System.out.println("法棍的原材料:面粉,酵母,鹽,水。");
    }

    @Override
    public void showTasteInfo() {
        System.out.println("表皮松脆,內心柔軟而稍具韌性,越嚼越香,充滿麥香味。");
    }
    
}

BreadFactory接口以及BreadFactory的類

// 面包工廠的接口
public interface BreadFactory {
    // 生產之后的返回類型為面包類
    Bread productBread();
}

// 奶油面包工廠接上面包工廠接口類生產奶油面包
class CreamBreadFactory implements BreadFactory{

    @Override
    public Bread productBread() {
        // 奶油面包生產過程
        System.out.println("生產奶油面包中···");
        // 生產完畢返回一個奶油面包類
        return new CreamBread();
    }

}

class CheeseBreadFactory implements BreadFactory{

    @Override
    public Bread productBread() {
        // 芝士面包生產過程
        System.out.println("生產芝士面包中···");
        // 生產完畢返回一個奶油面包類
        return new CheeseBread();
    }

}

class BaguetteFactory implements BreadFactory{

    @Override
    public Bread productBread() {
        // 法棍生產過程
        System.out.println("生產法棍中···");
        // 生產完畢返回一個法棍類
        return new Baguette();
    }
}

main

public class Test{
    public static void main(String[] args) {
        // 面包通過奶油面包加工廠來生產奶油面包
        Bread creamBread = new CreamBreadFactory().productBread();
        creamBread.showMaterialsInfo();
        creamBread.showTasteInfo();

        // 面包通過芝士面包加工廠來生產芝士面包
        Bread cheeseBread = new CheeseBreadFactory().productBread();
        cheeseBread.showMaterialsInfo();
        cheeseBread.showTasteInfo();
    }
}
工廠模式圖
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容