1. 程式人生 > 實用技巧 >為什麼wait()、notify()方法需要和synchronized一起使用

為什麼wait()、notify()方法需要和synchronized一起使用

  • 「MoreThanJava」 宣揚的是 「學習,不止 CODE」,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Java 基礎的一個總回顧,旨在 「幫助新朋友快速高質量的學習」
  • 當然 不論新老朋友 我相信您都可以 從中獲益。如果覺得 「不錯」 的朋友,歡迎 「關注 + 留言 + 分享」,文末有完整的獲取連結,您的支援是我前進的最大的動力!

Part 0. 搭建好開發環境

在一切開始之前,我們需要先搭建好我們的 開發環境 (從前文得知 Java 程式的執行需要 JVM,編寫 Java 程式碼需要 IDE)

,或者在您完全準備好之前可以 暫時使用 線上版本的 Java 環境來執行前面一些內容涉及的簡單程式碼:https://c.runoob.com/compile/10

安裝 JDK 並配置環境

JVM、JRE 和 JDK 有什麼關係?

  • JVM(Java Virtual Machine,Java 虛擬機器):是整個 Java 實現跨平臺的最核心的部分,能夠執行以 Java 語言寫作的軟體程式。
  • JRE(Java Runtime Environment,Java 執行環境):是執行 JAVA 程式所必須的環境的集合,包含 JVM 標準實現及 Java 核心類庫。
  • JDK(Java Development Kid,Java 開發開源工具包)
    :是針對 Java 開發人員的產品,是整個 Java 的核心,包括了 Java 執行環境 JRE、Java 工具和 Java 基礎類庫。

這三者的關係是一層層的巢狀關係:JDK > JRE > JVM

JDK 包含了 Java 的編譯器、偵錯程式等一系列開發工具,所以作為開發人員我們需要安裝 JDK,而某一些只需要執行編譯好的 Java 程式的伺服器則可以只安裝 JRE 即可 (極少數情況,通常還是安裝 JDK)

下載安裝 JDK

Java 程式必須執行在 JVM 之上,所以我們第一件事情就是安裝 JDK。我們選擇最新的 JDK 14 進行安裝:

選擇合適自己電腦平臺的 JDK 進行下載安裝即可:

配置環境

Windows 平臺

第一步

在 Windows 安裝之後需要額外 配置環境變數,首先【右鍵我的電腦】 → 選擇【屬性(R)】 → 開啟【高階系統設定】:

第二步

在【高階】標籤下選擇【環境變數】,並對環境變數【path】進行編輯操作:

第三步

新建環境變數,然後把剛才安裝 jdk 的安裝路徑複製進去,路徑截止到 bin 目錄:

第四步

快捷鍵【Win + R】輸入【cmd】調出 dos 視窗,輸入【java -version】進行驗證:

Mac 平臺

第一步

開啟蘋果 dos 視窗,先確認自己使用的 shellzsh 還是 bash,在命令列中輸入 echo $SHELL

  • 如果輸出 /bin/bash 則為 bash;
  • 如果輸出結果為 /bin/zsh 則為 zsh

第二步

根據上面不同的結果 修改 shell 配置檔案,若為 bash,則開啟 ~/.bash_profile,若為 zsh 則開啟 ~/.zshrc,在響應的檔案末尾新增以下內容,並儲存:

export JAVA_HOME=$(/usr/libexec/java_home)

第三步

~/ 目錄,命令列執行 source 命令:同樣如果是 bash,則執行 source .bash_profile,而如果是 zsh,則執行 source .zshrc讓剛才的修改生效

第四步

命令列執行 java -version 檢查是否配置成功:

Java 的不同版本

隨著 Java 的發展,SUN 公司給 Java 分出了三個不同版本:

  • Java SE(Standard Edition):標準版,包含標準的 JVM 和標準庫;
  • Java EE(Enterprise Edition):企業版,在 SE 的基礎上加了大量的 API 和庫,以方便開發 Web 應用、資料庫、訊息服務等;
  • Java ME(Micro Edition):是針對嵌入式裝置 "瘦身" 之後的 Java SE;

毫無疑問,Java SE 是 Java 平臺的核心,而 Java EE 是進一步學習 Web 應用所必須的。

安裝 IDEA 開發工具

體驗記事本編寫執行 Java 程式

Java 原始碼本質上其實就是普通的文字檔案,所以理論上來說任何可以編輯文字檔案的編輯器都可以作為我們的 Java 程式碼編輯工具。比如:Windows 記事本、Mac OS X 下的文字編輯、Linux 下的 vi 等。

但是這些簡單工具沒有「語法的高亮提示」、「自動完成」等功能,這些功能的缺失會 大大降低程式碼的編寫效率

第一步:新建一個 HelloWorld.java 檔案

然後輸入以下內容並儲存:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
  • 注意HelloWorld.java 檔名不允許出現空格,以及要保證和第一行 class 後跟著的字串 HelloWorld 保持一致;

第二步:編譯和執行

將 Java 源程式編寫並儲存到檔案之後,還需要進行編譯才能執行。編譯 Java 源程式使用的是 JDK 中的 javac 命令,例如編譯上節的 HelloWorld.java,完整命令如下:

javac HelloWorld.java

該命令會讓 Java 編譯器讀取 JavaWorld.java 檔案的原始碼,並把它編譯成符合 Java 虛擬機器規範的位元組碼檔案 (.class 檔案)

想要執行位元組碼檔案也需要使用 JDK 中的 java 命令,例如執行上面生成的 .class 檔案,完整命令如下:

java HelloWorld

具體效果如下:

更加智慧的 IDEA

儘管能夠使用文字編輯器來編寫我們的 Java 程式,但效率著實不敢恭維,所以我們一般使用更加先進的 整合開發工具 (俗稱 IDE,Integrated Development Environment)

不僅僅包含更加智慧的程式碼編輯器、編譯器、偵錯程式,還有友好的使用者介面以及各式各樣幫助我們提升效率的外掛。

對於效率這方面,下面我們就幾個方面簡單感受一下。

更友好的程式碼提示功能

不僅僅是基礎的關鍵字的提醒,IDEA 會基於當前的上下文 (也就是基於位於當前程式碼上下的程式碼進行分析),更加智慧的進行過濾和提醒:

強大的糾錯能力

我們總是會犯一些低階錯誤,比如一不留神打錯一個字母,可能找了好久都找不到錯誤所在,IDEA 的糾錯能力也許可以幫到你,看一個例子:

智慧提示重構程式碼

如果你寫的程式碼過於複雜,或者有更好的方式來替代你寫的程式碼,那麼 IDEA 會給你一個提示,告訴你還可以有更好的方式。如下圖:

一些酷炫的操作

比如你看我從頭寫一個 HelloWorld 程式:

這應該比一個一個字元敲快多了吧...(小聲bb:文章末尾有教程哦)

And More...

來一個總結:

驗證環境是否安裝成功

開啟【IDEA】新建一個空白的 Java 專案:

右鍵在【scr】目錄新建一個空白的【HelloIDEA】的 Java Class 檔案:

然後接下來像我這樣操作,來編寫一個【HelloIDEA】的 Java 程式:

  • psvm:是 public static void main 的縮寫;
  • sout:是 System.out 的縮寫;
  • 您在 IDEA 中鍵入以上單詞時會由 IDEA 提示智慧快速地完成輸入,以此來提升效率;

怕有些同學迷惑,點選左邊的綠三角會彈出如下的資訊,點選第一個選項就能夠執行啦:

Part 1. 識別符號和保留字

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

我們來看看剛才我們寫的 Hello World 程式。

第一行:public class HelloWorld {

  • 關鍵字 public:稱為 訪問修飾符 (access modifier),用來控制程式的其他部分對這一段程式碼的訪問級別 (這裡暫時理解為公用的,之後會更加詳細地介紹)
  • 關鍵字 class:表明 Java 程式中的全部內容都包含在類中 (之後會花很多功夫來說明類,這裡可以僅僅把類理解為程式邏輯的一個容器,程式邏輯定義了應用軟體的行為)
  • HelloWorld:關鍵字 class 後面緊跟的是類名,它除了必須跟檔名保持一致外,還應該遵循 Java 類名的命名規範 —— 類名以大寫字母開頭。如果名字由多個單片語成,每個單詞的第一個字母需要大寫
    • 例如:FirstSample 就遵循了 Java 類的命名規範的原則;
    • 這樣的 命名方式 被稱為 駝峰命名法 (camel case),首字母是大寫的則稱為 大駝峰命名法,首字母小寫的則稱為 小駝峰命名法 (如:firstSample,後續文章會提到的變數就採用這種方式)

類名命名規範:

  • BadhelloGood123Note_Book_World
  • GoodHelloTesterNoteBook

識別符號

在程式設計中,某個東西的名稱就被稱為 識別符號,例如上述的類名 HelloWorld。在 Java 中定義識別符號存在以下幾種規則:

  1. 只能由數字、字母、下劃線(_)和美元符號($)組成;
  2. 第一個字元不能是數字;
  3. 識別符號內不允許有空格;
  4. 不能使用 Java 保留字 (下方有列出 Java 中存在的保留字)

識別符號命名示範:

  • BadLady Luck (壞:識別符號內不允許有空格)x/y (錯誤:識別符號中不允許使用斜槓)1stPrize (錯誤:以數字開頭)abc (壞:沒有任何意義)_name (壞:不要以 _$ 開頭)
  • GooduserNameStudentManagerNoteBook

保留字

保留字就是像 class 這樣有特殊含義的識別符號,您只能將保留字用於其設定的專屬用途 (如 class 除了定義類,您將不能用作其他任何目的)

在 Java 中存在以下的保留字:(無需記憶,它們中的大部分都或多或少地出現在之後您的編碼中.. IDEA 也會有智慧的提示)

abstract class    extends implements null      strictfp     true
assert   const    false   import     package   super        try
boolean  continue final   instanceof private   switch       void
break    default  finally int        protected synchronized volatile
byte     do       float   interface  public    this         while
case     double   for     long       return    throw
catch    else     goto    native     short     throws
char     enum     if      new        static    transient

Part 2. 程式的基本結構和方法簡述

程式基本結構

在類的第一個大括號和最後一個大括號之間描述了程式所做的一切:

public class ClassName {
}

首先,每個原始碼檔案必須有一個主類 (名字同文件名),在之後的文章中我們會看到一個原始碼檔案可能會同時存在幾個類的情況發生,暫時不考慮。

在上文程式的第三行:public static void main ( String[] args ),展示了程式在何處開始執行,Java 虛擬機器總是從指定類的 main 方法的程式碼開始執行,因此為了程式碼能夠執行,在類的原檔案中必須包含一個 main 方法。

對這一行稍微做一下解釋:

  • public:訪問修飾符,用來描述該方法的訪問許可權級別,這裡為所有人都能訪問;
  • static:保留字,用來定義該方法是一個靜態方法;
  • void:用來描述該方法沒有任何的返回值;
  • main:方法名;
  • (String[] args):描述了該方法所接收的引數;

所以剛開始接觸的程式結構大概看起來像是下面這樣:

public class ClassName {
    public static void main(String[] args) {
        ......
    }
}

由於 Java 是大小寫敏感 的,所以 main 這個單詞不允許任何的修改。(Java 語言規範中規定 main 方法必須宣告為 public)

大括號的使用風格曾經引發過許多無意義的爭論,以下兩種風格,哪一種更好呢?:

public class ClassName {
}
public class ClassName 
{
}

沒有答案,雖然對於 Java 編譯器來說空白符會被省略,這兩種並無差別,但作為開發者的我們,選擇 Java 慣用的風格 (第一種) 就好了...

println()

考慮下面這一段程式碼,實際上就是上面的 HelloWorld 程式 main 方法中的語句:

{
    System.out.println("Hello World!");
}

一對大括號表示 方法體 的開始與結束,在這個方法中只包含一條語句。跟大多數程式設計語言一樣,可以把 Java 中的語句看成是語言中的一個句子。

在 Java 中,每個句子必須用分號結束 (英文分號)

特別需要說明,回車並不是語句的結束標誌,因此,如果需要可以將一條語句寫在多行上。

在這裡,我們使用了 System.out 這個物件並呼叫了它的 println 方法 (點號 . 用於呼叫方法)。它的作用是在螢幕上輸出指定的資訊。例如,我們想要輸出 我沒有三顆心臟 則可以這樣呼叫:

System.out.println("我沒有三顆心臟");

Java 使用方法的通用語法是:

object.method(parameters)

這一條語句的作用是在螢幕上輸出 Hello World!,這一部分由字元組成的序列 (其中不應該包含引號) 被稱為 字串。它可以包含任何有效字元,包括空格和標點符號。

方法簡述

方法 是根據語句構建的。程式通常包含許多方法。 我們的示例程式僅包含一種方法 (main 方法)。當然我們還呼叫了系統幫我們寫好的 System.out.println() 方法。

嘗試定義自己的方法

我們可以試著仿照 main 方法來寫一個自己的方法,假設我們想要輸出一段心理學三大巨頭之一阿德勒的一段話:

public static void printAdlerOneQuote() {
    System.out.println(
        "太在意別人的視線和評價,才會不斷尋求別人的認可。"
        + "對認可的追求,才扼殺了自由。"
        + "由於不想被任何人討厭,才選擇了不自由的生活方式。"
        + "換言之,自由就是不再尋求認可。"
    );
}

有幾點需要說明:

  • 方法名採用小駝峰命名法,且儘量保證有意義的命名:如果這裡把方法名修改成 abc 似乎就有點兒不知所云了;
  • 一行程式碼量不要超過可視距離:如果全部冗餘在一行,不僅看這一段程式碼會花額外的精力去翻看和理解,也會給其他閱讀程式碼的人造成困擾;
  • 建議:一個方法只做一件事情。您可以看到這個方法除了像方法名描述的那樣列印一句阿德勒的名言之外,沒有做其他任何的操作,這樣做能大大增加程式碼的可閱讀性;

在 main 方法中呼叫

正常的方法呼叫需要像上面提到的那樣:object.method(parameters),但由於身處同一個類中,this.printAdlerOneQuote(); 就可以簡寫成:printAdlerOneQuote();

完整的程式程式碼如下:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World!");
        printAdlerOneQuote();
    }

    public static void printAdlerOneQuote() {
        System.out.println(
            "太在意別人的視線和評價,才會不斷尋求別人的認可。"
                + "對認可的追求,才扼殺了自由。"
                + "由於不想被任何人討厭,才選擇了不自由的生活方式。"
                + "換言之,自由就是不再尋求認可。"
        );
    }
}

練習:嘗試輸出自己喜歡的一段話在螢幕中

參考答案:(上面的完整程式碼演示)

Part 3. 語法錯誤和 Bug

簡述語法錯誤

在原始檔中,字串文字必須僅在一行上。以下內容不合法,將無法編譯:

System.out.println("Hello "
    "World!");

您編寫的程式碼不符合 Java 的語法規定,就會發生 語法錯誤

在 Java 編譯器將原始碼檔案編譯成 .class 檔案之前,會預設幫你做許多工作,檢查語法就是其中一項。

例如,我們在桌面上試著建立一個【HelloWorld.java】檔案然後輸入一段存在錯誤的程式碼 (您可以試著檢查一下哪裡出現了一處錯誤)

public Class HelloWorld {
    
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

然後再當前目錄執行 javac HelloWorld.java 嘗試編譯這個存在錯誤的 Java 原始檔:

→ javac HelloWorld.java
HelloWorld.java:1: 錯誤: 需要class, interface或enum
public Class HelloWorld {
       ^
HelloWorld.java:3: 錯誤: 需要class, interface或enum
    public static void main(String[] args) {
                  ^
HelloWorld.java:6: 錯誤: 需要class, interface或enum
    }
    ^
3 個錯誤

Java 編譯器提示我們有三處錯誤,實際上我們也確實粗心地把 class 錯誤的寫成了 Class

編譯器也未建立新的位元組碼檔案 (.class),因為在遇到錯誤時它將停止翻譯。

Bug 簡述

僅僅因為程式通過編譯並且成功執行並不意味著它是正確的。

例如,您的任務是建立一個在螢幕上輸出 Hello Wrold!,但是您卻錯誤地寫成了 Hello Bug!執行時沒有按照預期執行,則就稱該程式存在 Bug!

Bug 起源

Bug 一詞的原意是 “臭蟲” 或 “蟲子”。

第一個有記載的 Bug 是美國海軍程式設計員、編譯器的發明者格蕾斯·哈珀 (GraceHopper) 發現的。

194599 日,下午三點。哈珀中尉正領著她的小組構造一個稱為“馬克二型”的計算機。這還不是一個真正的電子計算機,它使用了大量的繼電器,一種電子機械裝置。第二次世界大戰還沒有結束。哈珀的小組日以繼夜地工作。機房是一間第一次世界大戰時建造的老建築。那是一個炎熱的夏天,房間沒有空調,所有窗戶都敞開散熱。

突然,馬克二型宕機了。技術人員試了很多辦法,最後定位到第 70 號繼電器出錯。哈珀觀察這個出錯的繼電器,發現一隻飛蛾躺在中間,已經被繼電器打死。她小心地用攝子將蛾子夾出來,用透明膠布帖到「事件記錄本」中,並註明「第一個發現蟲子的例項」。

從此以後,人們將計算機錯誤稱為 Bug,與之相對應,人們將發現 Bug 並加以糾正的過程叫做 “Debug”,意即「捉蟲子」或「殺蟲子」。

Bug 是怎麼產生的?

來幾個清奇的段子吧。

段子一:龐博脫口秀大會解釋 Bug

B 站自取:https://www.bilibili.com/video/BV1oJ411S7o4?from=search&seid=7940414495637079568

段子二:測試工程師們來到一家酒吧

引用自知乎@第七地區的答案:https://www.zhihu.com/question/365343579/answer/967299388

段子三:領導讓我修房子

引用自知乎@噠柏的答案:https://www.zhihu.com/question/365343579/answer/967299388

地球人和程式設計師眼中改 Bug 的不同

修改程式中的 Bug,要經過三個步驟:

  1. 找到它;
  2. 想辦法解決它;
  3. 確認它已經被解決 (並且沒有引入其它問題)

說起來比較簡單,在地球人 (程式設計師等同於外星人) 看起來,過程是這樣的:

但是,對於程式設計師來說,找一個 Bug 往往是這樣的:

找到之後呢,解決這個 Bug 又是一個難題:

換個柱子什麼的比較簡單,還有更崩潰的!

Part 4. 註釋

註釋 是寫程式的人留下的閱讀筆記。

通常註釋以兩個字元 // 開頭。Java 編譯器將忽略那些字元以及在該行之後的所有字元。例如:

public class HelloWorld {

    // 程式入口
    public static void main(String[] args) {
        // 輸出 Hello World!
        System.out.println("Hello World!");
    }
}

這一段程式碼跟我們最開始的 HelloWrold 程式完全相同。大多數的程式編輯器 (例如 IDEA) 都足夠聰明,可以識別註釋並將其顯示為無關緊要的一些顏色:

與大多數程式設計語言一樣,Java 中的註釋也不會出現在可執行程式中。因此,可以在源程式中根據需要新增任意多的註釋,而不必擔心可執行程式碼會膨脹。

三種註釋的方式

/**
 * 文件註釋
 * 可以註釋多條內容
 */
public static void main(String[] args) {
    // 這是單行註釋
    System.out.println("演示三種註釋方式");
    /*  
    這是多行註釋
     */
}
  • 單行註釋:以 // 標識,只能註釋一行內容;
  • 多行註釋:包含在 /**/ 之間,能註釋多行內容,為了提高可閱讀性,一般首行和尾行不寫註釋資訊;
  • 文件註釋:包含在 /** (兩個 *)*/ 之間,也能註釋多行內容,一般用在類、方法和變數上面,用來描述其作用 (這是 Java 的一種規範,之後會更多的見識到)

幾點建議

註釋的目的是: 儘量幫助讀者瞭解得和作者一樣多。 —— 《編寫可讀程式碼的藝術》

以下節選自《阿里巴巴 Java 開發手冊》對於註釋的幾點要求:

  1. 【強制】 類、雷屬性、類方法的註釋必須使用 Javadoc 規範,使用 /**內容*/ 格式,不得使用 // 方式 (上面的演示程式中就不符合規範,需要注意)
  2. 【強制】 方法內部的單行註釋,在被註釋語句上方另起一行,使用 // 註釋。方法內部的多行註釋,使用 /* */ 並注意與程式碼對齊;
  3. 【推薦】 與其用「半吊子」英文來註釋,不如用中文註釋把問題說清楚。專有名詞與關鍵字保持英文原文即可;
  4. 【推薦】 在修改程式碼的同時,要對註釋進行相應的修改;
  5. 【參考】 對於註釋的要求:
    1. 能夠準確反映設計思想和程式碼邏輯;
    2. 能夠描述業務含義,使其他程式設計師能夠迅速瞭解程式碼背後的資訊。完全沒有註釋的大段程式碼對於閱讀者如同天書;註釋是給自己看的,應做到即使間隔很長時間,也能清晰理解當時的思路;註釋也是給繼任者看得,使其能夠快速接替自己的工作;
  6. 【參考】 好的命名、程式碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋。因為程式碼的邏輯一旦修改,修改註釋也是相當大的負擔;

對於以上的一些建議,我相信在您之後的程式設計之路上會越發地體會深刻。

要點回顧

  1. JVM、JRE、JDK 的說明和聯絡;
  2. Java 開發環境的搭建方法;
  3. 識別符號和保留字的定義以及識別符號的命名規範;
  4. 程式的基本結構和方法定義和呼叫的簡單方法;
  5. 語法錯誤和 Bug (起源、怎麼產生的)
  6. 註釋的定義、三種註釋的方式、註釋的規範;

練習

練習 1:嘗試輸出自己喜歡的一段話在螢幕中

參考答案:(上面有完整程式碼演示)

練習 2:嘗試把上方輸出的內容單獨實現為自己的方法並在 main 方法中呼叫執行

參考答案:(上面有完整程式碼演示)

練習 3:給自己的程式碼新增上註釋並讓朋友閱讀詢問是否清晰必要

參考答案:(上面有完整程式碼演示)

自取資料

相關擴充套件閱讀資料

  1. Java 教程 | 廖雪峰官方網站 - https://www.liaoxuefeng.com/wiki/1252599548343744
  2. 史上最簡單的 Intellij IDEA 教程 - https://github.com/judasn/IntelliJ-IDEA-Tutorial
  3. 計算機發展史上十大著名軟體缺陷 - https://zhuanlan.zhihu.com/p/31167236

推薦書籍

Java 核心技術·卷 I(原書第 11 版)

推薦理由: 這本書在知識體系完整充實的同時,又比《Thinking in Java》暴風式的知識洗禮來得輕鬆,新人入門書籍強烈推薦!

可讀程式碼的藝術

推薦理由: 編寫可閱讀的程式碼是程式設計師從始至終需要提升的能力,這本書完整呈體系的結構,和樸實充實的例項,讓讀者通過閱讀就能在實踐中真實地運用起來,推薦!

參考資料

  1. 《Thinking in Java》第四版
  2. 《Java 核心技術 卷 I》第11版
  3. 廖雪峰系列 Java 教程 - https://www.liaoxuefeng.com/wiki/1252599548343744
  4. 歷史上的第一個計算機Bug - http://www.cxyym.com/2014/11/999/
  • 本文已收錄至我的 Github 程式設計師成長系列 【More Than Java】,學習,不止 Code,歡迎 star:https://github.com/wmyskxz/MoreThanJava
  • 個人公眾號 :wmyskxz,個人獨立域名部落格:wmyskxz.com,堅持原創輸出,下方掃碼關注,2020,與您共同成長!

非常感謝各位人才能 看到這裡,如果覺得本篇文章寫得不錯,覺得 「我沒有三顆心臟」有點東西 的話,求點贊,求關注,求分享,求留言!

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見!