一、數據類型
??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():喚醒正在排隊等候同步資源的線程中優先級最高者結束等待。
????????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 == true
→ A線程執行 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 == true
→ A線程執行 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();
}
}