網(wǎng)絡(luò)廣告網(wǎng)站怎么做百度小說排行榜總榜
???????
目錄
線程狀態(tài)
觀察線程的所有狀態(tài)
線程狀態(tài)及其描述
線程狀態(tài)轉(zhuǎn)換
代碼示例1
代碼示例2
線程安全?
概念
線程不安全的代碼示例
線程不安全的原因
線程安全的代碼示例-加鎖
synchronized關(guān)鍵字
synchronized的特性
小結(jié)
形成死鎖的四個必要條件
synchronized的使用示例
Java標(biāo)準(zhǔn)庫中的線程安全類
線程狀態(tài)
觀察線程的所有狀態(tài)
線程的狀態(tài)是一個枚舉類型 Thread.State
public class Demo11 {public static void main(String[] args) {for(Thread.State state:Thread.State.values())System.out.println(state);}
}
運(yùn)行結(jié)果
線程狀態(tài)及其描述
NEW:Thread對象已經(jīng)有了,但是start方法還沒調(diào)用;
RUNNABLE:就緒狀態(tài),線程已經(jīng)在CPU上執(zhí)行了/線程正在排隊(duì)等待執(zhí)行(即工作中或即將開始工作);
TERMINATED:Thread對象還在,但是內(nèi)核中的下線程已經(jīng)沒了,即工作完成了;
TIMED_WARTING:阻塞狀態(tài),由于sleep這種固定時間的方式產(chǎn)生的阻塞;
WAITING:阻塞,由于wait這種不固定時間的方式產(chǎn)生的阻塞;
BLOCKED:阻塞,由于鎖競爭導(dǎo)致的阻塞。
線程狀態(tài)轉(zhuǎn)換
代碼示例1
public class Demo11 {public static void main(String[] args) {Object object=new Object();Thread t1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (object){while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}},"t1");t1.start();Thread t2=new Thread(new Runnable() {@Overridepublic void run() {synchronized (object){System.out.println("hello");}}},"t2");t2.start();}
}
通過jconsole可以看到t1的狀態(tài)是TIMED_WAITING,t2的狀態(tài)是BLOCKED。
代碼示例2
public class Demo11 {public static void main(String[] args) {Object object=new Object();Thread t1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (object){while(true){try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}},"t1");t1.start();}
}
通過jconsole可以看到t1的狀態(tài)是WAITING.
小結(jié)
BLOCKED表示等待獲取鎖,WAITING和TIMED_WAITING表示等待其它線程發(fā)來通知;
TIMED_WAITING線程在等待喚醒,但設(shè)置了時限;
WAITING線程在無限等待喚醒。
線程安全?
概念
如果多線程環(huán)境下代碼運(yùn)行的結(jié)果是符合我們預(yù)期的,即在單線程環(huán)境應(yīng)該的結(jié)果,則說這個程序是線程安全的。
線程不安全的代碼示例
public class Demo12 {private static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});Thread t2=new Thread(()->{for (int i = 0; i < 50000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count: "+count);}
}
運(yùn)行結(jié)果
結(jié)果與預(yù)期結(jié)果不一致且差別很大,顯然上述代碼是線程不安全的。
線程不安全的原因
1.修改共享數(shù)據(jù)
上述代碼涉及到多線程(兩個及兩個以上的線程)針對同一個變量count進(jìn)行修改。
2.原子性
一條Java語句不一定是原子的,也不一定只是一條指令。
比如count++,其實(shí)是由三步操作組成的:
1.從內(nèi)存中把數(shù)據(jù)讀取到CPU;
2.對變量count進(jìn)行++;
3.把數(shù)據(jù)寫回到內(nèi)存。
如果一個線程正在對一個變量操作,中途其它線程插入進(jìn)來了,如果這個操作被打斷,結(jié)果就可能是錯誤的。
3.可見性
可見性指, 一個線程對共享變量值的修改,能夠及時地被其他線程看到.
Java 內(nèi)存模型 (JMM): Java虛擬機(jī)規(guī)范中定義了Java內(nèi)存模型.?
目的是屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的并發(fā)效果.
線程之間的共享變量存在 主內(nèi)存 (Main Memory).?
每一個線程都有自己的 "工作內(nèi)存" (Working Memory) .?
當(dāng)線程要讀取一個共享變量的時候, 會先把變量從主內(nèi)存拷貝到工作內(nèi)存, 再從工作內(nèi)存讀取數(shù)據(jù).?
當(dāng)線程要修改一個共享變量的時候, 也會先修改工作內(nèi)存中的副本, 再同步回主內(nèi)存.?
由于每個線程有自己的工作內(nèi)存, 這些工作內(nèi)存中的內(nèi)容相當(dāng)于同一個共享變量的 "副本". 此時修改線程1 的工作內(nèi)存中的值, 線程2 的工作內(nèi)存不一定會及時變化.
4.指令重排序
如果是在單線程情況下,JVM、CPU指令集會對其進(jìn)行優(yōu)化,比如,原來是按1->2->3的方式執(zhí)行,優(yōu)化后可能會按 1->3->2的方式執(zhí)行,也是沒問題,可以少跑一次前臺。這種叫做指令重排序。
編譯器對于指令重排序的前提是 "保持邏輯不發(fā)生變化". 這一點(diǎn)在單線程環(huán)境下比較容易判斷, 但
是在多線程環(huán)境下就沒那么容易了, 多線程的代碼執(zhí)行復(fù)雜程度更高, 編譯器很難在編譯階段對代
碼的執(zhí)行效果進(jìn)行預(yù)測, 因此激進(jìn)的重排序很容易導(dǎo)致優(yōu)化后的邏輯和之前不等價(jià).
線程安全的代碼示例-加鎖
public class Demo12 {private static int count=0;public static void main(String[] args) throws InterruptedException {Object locker=new Object();Thread t1=new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});Thread t2=new Thread(()->{for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count: "+count);}
}
運(yùn)行結(jié)果
synchronized關(guān)鍵字
synchronized的特性
1)互斥
synchronized 會起到互斥效果, 某個線程執(zhí)行到某個對象的 synchronized 中時, 其他線程如果也執(zhí)行到同一個對象 synchronized 就會阻塞等待.?
進(jìn)入 synchronized 修飾的代碼塊, 相當(dāng)于 加鎖。
退出 synchronized 修飾的代碼塊, 相當(dāng)于 解鎖。
synchronized用的鎖是存在Java對象頭里的。
2)可重入
synchronized 同步塊對同一條線程來說是可重入的,不會出現(xiàn)自己把自己鎖死的問題;
理解死鎖
// 第一次加鎖, 加鎖成功
lock();
// 第二次加鎖, 鎖已經(jīng)被占用, 阻塞等待.
lock();
static class Counter {public int count = 0;synchronized void increase() {count++;}synchronized void increase2() {increase();}
}
在上面的代碼中,?increase 和 increase2 兩個方法都加了 synchronized, 此處的 synchronized 都是針對 this 當(dāng)前對象加鎖的.?
在調(diào)用 increase2 的時候, 先加了一次鎖, 執(zhí)行到 increase 的時候, 又加了一次鎖. (上個鎖還沒釋
放, 相當(dāng)于連續(xù)加兩次鎖),這個代碼是完全沒問題的. 因?yàn)?synchronized 是可重入鎖.
小結(jié)
在可重入鎖的內(nèi)部, 包含了 "線程持有者" 和 "計(jì)數(shù)器" 兩個信息.?
如果某個線程加鎖的時候, 發(fā)現(xiàn)鎖已經(jīng)被人占用, 但是恰好占用的正是自己, 那么仍然可以繼續(xù)獲取到鎖, 并讓計(jì)數(shù)器自增.?
解鎖的時候計(jì)數(shù)器遞減為 0 的時候, 才真正釋放鎖. (才能被別的線程獲取到)
形成死鎖的四個必要條件
死鎖是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。
1.互斥條件(鎖的基本特性)
? ?當(dāng)一個線程持有一把鎖之后,另一個線程也想要獲取到鎖,就要阻塞等待。
2.不可搶占條件(鎖的基本特性)
? ?當(dāng)鎖已經(jīng)被線程1拿到之后,線程2只能等線程1主動釋放,不能強(qiáng)行搶過來。
3.請求與保持條件(代碼結(jié)構(gòu))
? ?一個線程嘗試獲取多把鎖,已經(jīng)獲取到部分?jǐn)?shù)量的鎖,但仍嘗試獲取其它線程已經(jīng)占有的鎖。
4.循環(huán)等待/環(huán)路等待(代碼結(jié)構(gòu))
? ?等待的依賴關(guān)系,形成了環(huán)。
這四個條件同時滿足時,系統(tǒng)中就可能發(fā)生死鎖。
解決死鎖的方法通常包括死鎖預(yù)防、死鎖避免、死鎖檢測和死鎖恢復(fù)等策略。?
比如包括調(diào)整代碼結(jié)構(gòu),避免循環(huán)等待;對鎖進(jìn)行編號,先加編號大的鎖或編號小的鎖。
synchronized的使用示例
?synchronized 本質(zhì)上要修改指定對象的 "對象頭". 從使用角度來看, synchronized 也勢必要搭配一個具體的對象來使用.
1.直接修飾普通方法
public class SynchronizedDemo {public synchronized void methond() {}
}
2.直接修飾靜態(tài)方法
public class SynchronizedDemo {public synchronized static void method() {}
}
3.修飾代碼塊
鎖當(dāng)前對象
public class SynchronizedDemo {public void method() {synchronized (this) {}}
}
鎖類對象
public class SynchronizedDemo {public void method() {synchronized (SynchronizedDemo.class) {}}
}
我們重點(diǎn)要理解,synchronized 鎖的是什么. 兩個線程競爭同一把鎖, 才會產(chǎn)生阻塞等待.?
兩個線程分別嘗試獲取兩把不同的鎖, 不會產(chǎn)生競爭.
Java標(biāo)準(zhǔn)庫中的線程安全類
ArrayListLinkedListHashMapTreeMapHashSetTreeSetStringBuilder
Vector ( 不推薦使用 )HashTable ( 不推薦使用 )ConcurrentHashMapStringBuffer
我們可以看到,例如StringBuffer類的成員,有不少是加鎖的: