并發(fā)核心理論
1.共享性: 數(shù)據(jù)共享是線程安全問題的主要問題之一
2.互斥性: 資源互斥指同一時間內(nèi)值允許一個訪問者訪問.通常情況修改數(shù)據(jù)是互斥性晦譬,讀數(shù)據(jù)不要求.因此我們有共享鎖和互斥鎖.也叫讀鎖和寫鎖.
-
3.原子性:指操作是一個獨(dú)立右冻,不可分割的整體.操作不會中斷纯出,數(shù)據(jù)不會執(zhí)行一半的時候被其他操作修改.例如一條指令就是最基本的原子.但是 i++,就分了好幾次操作:
讀取i=1值 -->∨航睢2.i=1+1 --->3.存儲i=2
-
4.可見性:
image.png
每個線程都有自己的工作內(nèi)存,對于共享變量梳码,先拿到變量的副本隐圾,對副本進(jìn)行操作,在某一個時間在把副本的值同步到共享變量.這樣導(dǎo)致边翁,有可能線程1修改了共享變量的值翎承,某一時間線程2可能拿不到最新的值.
5.有序性:為了提高性能,編譯器和處理器可能會對指令進(jìn)行重排.
synchronized及其實(shí)現(xiàn)原理
synchronized 作用:
- 1.確保線程互斥的訪問同步代碼
- 2.保證共享變量能及時可見
- 3.解決重排序問題
synchronize 的一般用法符匾,對象鎖,類鎖.(synchronized Object,synchronized 方法)
對class文件反編譯后的匯編語言如下:
說明synchronized 是在對象前面加了 monitor .
Monitorenter : 對象都享有一個monitor
1.線程一旦進(jìn)入monitor瘩例,monitor值1啊胶,線程為monitor擁有者.
2.其他線程到了這里,會進(jìn)行阻塞垛贤,直到monitor的值=0.
Monitorexit:monitor 變回0.說明其他線程可以擁有.
對方法加synchronized
Synchronized底層優(yōu)化(偏向鎖焰坪、輕量級鎖)
鎖 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級的差距聘惦。 | 如果線程間存在鎖競爭某饰,會帶來額外的鎖撤銷的消耗。 | 適用于只有一個線程訪問同步塊場景善绎。 |
輕量級鎖 | 競爭的線程不會阻塞黔漂,提高了程序的響應(yīng)速度。 | 如果始終得不到鎖競爭的線程使用自旋會消耗CPU禀酱。 | 追求響應(yīng)時間炬守。同步塊執(zhí)行速度非常快剂跟。 |
重量級鎖 | 線程競爭不使用自旋减途,不會消耗CPU。 | 線程阻塞曹洽,響應(yīng)時間緩慢鳍置。 | 追求吞吐量。同步塊執(zhí)行時間較長送淆。 |
鎖的狀態(tài): 無鎖狀態(tài)税产,偏向鎖 ,輕量級鎖,重量級鎖
隨著鎖的競爭砖第,鎖的狀態(tài)可以升級撤卢,但是是單向的.從低到高,不會出現(xiàn)鎖的降級.
偏向鎖<輕量級鎖<重量級鎖
輕量級鎖:本意使用操作系統(tǒng)互斥量解決傳統(tǒng)的重量級鎖的性能問題.
使用場景:線程交替執(zhí)行同步塊.如果同一時刻訪問同一鎖的情況梧兼,輕量級鎖會升級重量級鎖.
偏向鎖:用于一個線程執(zhí)行同步塊是提高性能.一旦出現(xiàn)多線程競爭放吩,升級為輕量級鎖.
總結(jié) :JDk中采用輕量級鎖和偏向鎖等對Synchronized的優(yōu)化,但是這兩種鎖也不是完全沒缺點(diǎn)的羽杰,比如競爭比較激烈的時候渡紫,不但無法提升效率,反而會降低效率考赛,因?yàn)槎嗔艘粋€鎖升級的過程惕澎,這個時候就需要通過-XX:-UseBiasedLocking來禁用偏向鎖。下面是這幾種鎖的對比:
鎖 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不需要額外的消耗颜骤,和執(zhí)行非同步方法比僅存在納秒級的差距唧喉。 | 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗忍抽。 | 適用于只有一個線程訪問同步塊場景八孝。 |
輕量級鎖 | 競爭的線程不會阻塞,提高了程序的響應(yīng)速度鸠项。 | 如果始終得不到鎖競爭的線程使用自旋會消耗CPU干跛。 | 追求響應(yīng)時間。同步塊執(zhí)行速度非乘畎恚快楼入。 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU牧抽。 | 線程阻塞嘉熊,響應(yīng)時間緩慢。 | 追求吞吐量阎姥。同步塊執(zhí)行時間較長记舆。 |
Java并發(fā)編程:線程間的協(xié)作(wait/notify/sleep/yield/join)
線程有5中狀態(tài):
新建(New)-->準(zhǔn)備狀態(tài)(Runnable)-->運(yùn)行狀態(tài)(Running)-->阻塞(Blocking)-->死亡(Dead)
new :創(chuàng)建 new Thread();
Runnable:調(diào)用start(),進(jìn)入就緒狀態(tài),等待cpu分配資源呼巴,有系統(tǒng)運(yùn)行時線程來調(diào)度
Running:開始執(zhí)行Run方法
Blocking: 阻塞
Dead
Wait/notify/notifyAll()
wait() 當(dāng)前線程掛起泽腮,直到有其他線程調(diào)用notify(), notifyAll().
wait(long timeout) 當(dāng)前線程掛起,直到有其他線程調(diào)用notify(), notifyAll();或者等到timeout
wait(long timeout,int nanos)
notify() 喚醒指定線程 , notifyAll() 喚醒所有的線程
注意:wait 必須在synchronized 塊之內(nèi).
Thread.sleep(long sleeptime)
當(dāng)前線程暫停指定的時間(毫秒),而更深層次的區(qū)別在于sleep方法只是暫時讓出CPU的執(zhí)行權(quán)衣赶,并不釋放鎖.而wait方法則需要釋放鎖,(意味其他線程可以拿到鎖诊赊,進(jìn)行操作)
package jni.test.leon.javatest;
/**
* Created by leon on 17-12-11.
*/
public class SleepTest {
public synchronized void sleepMethod() {
System.out.println("Sleep start-----");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sleep end-----");
}
public void waitMethod() {
System.out.println("Wait start-----");
synchronized (this) {
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Wait end-----");
}
public static void main(String[] args) {
final SleepTest test1 = new SleepTest();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
test1.sleepMethod();
}
}).start();
}
try {
Thread.sleep(10000);//暫停十秒,等上面程序執(zhí)行完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----分割線-----");
final SleepTest test2 = new SleepTest();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
test2.waitMethod();
}
}).start();
}
}
}
//output-----
Connected to the target VM, address: '127.0.0.1:38691', transport: 'socket'
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
-----分割線-----
Wait start-----
Wait start-----
Wait start-----
Wait end-----
Disconnected from the target VM, address: '127.0.0.1:38691', transport: 'socket'
Wait end-----
Wait end-----
Process finished with exit code 0
Thread.yeild()
當(dāng)前線程暫停府瞄,讓其他線程有機(jī)會執(zhí)行.(用的場景比較少碧磅,主要是調(diào)試)
package jni.test.leon.javatest;
/**
* Created by leon on 17-12-11.
*/
public class YieldTest implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.yield();
}
}
public static void main(String[] args) {
YieldTest runn = new YieldTest();
Thread t1 = new Thread(runn, "FirstThread");
Thread t2 = new Thread(runn, "SecondThread");
t1.start();
t2.start();
}
}
//--output
FirstThread: 0
FirstThread: 1
SecondThread: 0
FirstThread: 2
SecondThread: 1
FirstThread: 3
SecondThread: 2
FirstThread: 4
SecondThread: 3
SecondThread: 4
Thread.join()/Thread.join(long waittime)/Thread.join(long waittime,int nano)
作用:父線程等待子線程執(zhí)行完之后在執(zhí)行碘箍,可以達(dá)到異步子線程,最后的同步.
package jni.test.leon.javatest;
/**
* Created by leon on 17-12-11.
*/
public class JoinTest implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + " start-----");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " end------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread test = null;
for (int i = 0; i < 5; i++) {
test = new Thread(new JoinTest());
test.start();
}
//這里是阻塞
try {
test.join(); //調(diào)用join方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished~~~");
}
}
//output------
Thread-0 start-----
Thread-1 start-----
Thread-2 start-----
Thread-3 start-----
Thread-4 start-----
Thread-0 end------
Thread-1 end------
Thread-2 end------
Thread-3 end------
Thread-4 end------
Finished~~~
總結(jié):
因?yàn)閣ait() /notify () 是對象monitor,所以當(dāng)wait的時候就monitorEnter,必須等待有monitorExit 才能釋放對象的擁有權(quán).所以鲸郊,一旦wait 了丰榴,其他的線程是無法獲得擁有權(quán),也就不能進(jìn)入.
而sleep, yield這些是Thread級別的方法秆撮,只是讓出cpu執(zhí)行權(quán)join四濒,調(diào)用的是wait 方法,所以是會進(jìn)行對象級別的monitor.
volatile的使用及其原理
我們知道 可見性职辨,有序性盗蟆,原子性 的問題.synchronized 就是用來解決這些問題的,但是synchronized是比較重量級舒裤,volatile是一個輕量級的解決有序性喳资,可見性,原子性的方案.
原理:
1.對有序性:
在解釋這個問題前腾供,我們先來了解一下Java中的happen-before規(guī)則仆邓,JSR 133中對Happen-before的定義如下:
Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.
通俗一點(diǎn)說就是如果a happen-before b,則a所做的任何操作對b是可見的台腥。(這一點(diǎn)大家務(wù)必記住宏赘,因?yàn)閔appen-before這個詞容易被誤解為是時間的前后)。我們再來看看JSR 133中定義了哪些happen-before規(guī)則:
- Each action in a thread happens before every subsequent action in that thread.
- An unlock on a monitor happens before every subsequent lock on that monitor.
- A write to a volatile field happens before every subsequent read of that volatile.
- A call to start() on a thread happens before any actions in the started thread.
- All actions in a thread happen before any other thread successfully returns from a join() on that thread.
- If an action a happens before an action b, and b happens before an action c, then a happens before c.
翻譯過來為:
- 同一個線程中的黎侈,前面的操作 happen-before 后續(xù)的操作。(即單線程內(nèi)按代碼順序執(zhí)行闷游。但是峻汉,在不影響在單線程環(huán)境執(zhí)行結(jié)果的前提下,編譯器和處理器可以進(jìn)行重排序脐往,這是合法的休吠。換句話說,這一是規(guī)則無法保證編譯重排和指令重排)业簿。
- 監(jiān)視器上的解鎖操作 happen-before 其后續(xù)的加鎖操作.(Synchronized 規(guī)則)
- 對volatile變量的寫操作 happen-before 后續(xù)的讀操作瘤礁。(volatile 規(guī)則)
- 線程的start() 方法 happen-before 該線程所有的后續(xù)操作。(線程啟動規(guī)則)
- 線程所有的操作 happen-before 其他線程在該線程上調(diào)用 join 返回成功后的操作梅尤。
- 如果 a happen-before b柜思,b happen-before c,則a happen-before c(傳遞性)巷燥。
這里我們主要看下第三條:volatile變量的保證有序性的規(guī)則<<Java并發(fā)編程:核心理論>>
2.可見性
使用 Volatile 關(guān)鍵字
1.修改時會強(qiáng)制修改主內(nèi)存的數(shù)據(jù)
2.修改變量后導(dǎo)致其他線程工作中內(nèi)存的值失效赡盘,所以需要重新讀取主內(nèi)存的數(shù)據(jù)
volatile 的使用場景比較有限:
1.該變量寫操作不依賴當(dāng)前值
2.該變量沒有包含在具有其他變量的不變式中(這個變量只有原子性操作,簡單說來就是 進(jìn)行賦值)
常用:
1.狀態(tài)標(biāo)記量
A:
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
B:
volatile boolean inited = false;
//線程1:
context = loadContext();
inited = true;
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
2.double check
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}