在現代計算機中每每存在多個CPU
核心,而1
個CPU
能同時運行一個線程,爲了充分利用CPU
多核心,提升CPU
的效率,多線程就應時而生了。java
那麼多線程就必定比單線程快嗎?答案是不必定,由於多線程存在單線程沒有的問題編程
CPU
給每一個線程分配的時間片很短,一般是幾十毫秒(ms),那麼線程的切換就會很頻繁。A
和線程B
都在互相等待對方釋放鎖,死鎖會形成系統不可用。1Mb/s
,資源的服務器帶寬只有2Mb/s
,那麼開10
個線程下載資源並不會將下載速度提高到10Mb/s
。既然多線程存在這些問題,那麼咱們在開發的過程當中有必要使用多線程嗎?咱們知道任何技術都有它存在的理由,總而言之就是多線程利大於弊,只要咱們合理使用多線程就能達到事半功倍的效果。bash
多線程的意思就是多個線程同時工做,那麼多線程之間如何協同合做,這也就是咱們須要解決的線程通訊、線程同步問題服務器
synchronized
是Java的關鍵字,可用於同步實例方法、類方法(靜態方法)、代碼塊多線程
synchronized
修飾實例方法的時候,同步的範圍是當前實例的實例方法。synchronized
修飾類方法的時候,同步的範圍是當前類的方法。synchronized
修飾代碼塊的時候,同步的範圍是()
中的對象。"talk is cheap show me the code"
讓咱們分別運行個例子來看看。併發
synchronized public void synSay() {
System.out.println("synSay----" + Thread.currentThread().getName());
while (true) { //保證進入該方法的線程 一直佔用着該同步方法
}
}
public void say() {
System.out.println("say----" + Thread.currentThread().getName());
}
public static void main(String[] args){
Test test1 = new Test();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test1.synSay();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000); //休眠3秒鐘 保證線程t1先執行
} catch (InterruptedException e) {
e.printStackTrace();
}
test1.say();
test1.synSay();
}
});
t1.start();
t2.start();
}
複製代碼
運行輸出ide
synSay----Thread-0 //線程t1
say----Thread-1 //線程t2
複製代碼
建立t1
,t2
兩個線程,分別執行同一個實例test1
的方法,線程t1
先執行加了同步關鍵字的synSay
方法,注意方法裏面須要加上個while
死循環,目的是讓線程一直在同步方法裏面,而後然線程t1執行以後再讓線程t2去執行,此時線程t2並不能成功進入到synSay
方法裏面,由於此時線程t1正在方法裏面,線程2只能在synSay
方法外面阻塞,可是線程t2能夠進入到沒有加同步關鍵字的say
方法。
也就是說關鍵字synchronized
修飾實例方法的時候,鎖住的是該實例的加了同步關鍵字的方法,而沒有加同步關鍵字的方法,線程仍是能夠正常訪問的。可是不一樣實例之間同步是不會影響的,由於每一個實例都有本身的一個鎖,不一樣實例之間的鎖是不同的。學習
synchronized static public void synSay() {
System.out.println("static synSay----" + Thread.currentThread().getName());
while (true) { //保證進入該方法的線程 一直佔用着該同步方法
}
}
synchronized public void synSay1() {
System.out.println("synSay1----" + Thread.currentThread().getName());
}
public void say() {
System.out.println("say----" + Thread.currentThread().getName());
}
public static void main(String[] args){
Test test1 = new Test();
Test test2 = new Test();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test1.synSay();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000); //休眠3秒鐘 保證線程t1先執行
} catch (InterruptedException e) {
e.printStackTrace();
}
test1.say();
test2.say();
test1.synSay();
}
});
t1.start();
t2.start();
}
複製代碼
運行輸出ui
static synSay----Thread-0 //線程t1 實例test1
say----Thread-1 //線程t2 實例test1
say----Thread-1 //線程t2 實例test2
static synSay----Thread-0 //線程t1 實例test1
say----Thread-1 //線程t2 實例test1
synSay1----Thread-1 //線程t2 實例test1
say----Thread-1 //線程t2 實例test2
複製代碼
這裏和上面的同步實例方法的代碼差很少,就是將synSay
方法加上了static
修飾符,即把方法從實例方法變成類方法了,而後咱們再新建個實例test2
,先讓線程t1調用實例test1的synSay類方法,在讓線程t2去調用實例test1的say實例方法、synSay類方法和讓線程t2去調用實例test2的say實例方法,發現在線程t1佔用加了同步關鍵字的synSay
類方法的時候,別的線程是不能調用加了鎖的類方法的,可是能夠調用沒有加同步關鍵字的方法或者加了同步關鍵字的實例方法,也就是說每一個類有且僅有11
個鎖,每一個實例有且僅有1
個鎖,可是每一個類能夠有一個或者多個實例,類的鎖和實例的鎖不會相互影響,實例之間的鎖也不會相互影響。須要注意的是,一個類和一個實例有且僅有一個鎖,當這個鎖被其餘線程佔用了,那麼別的線程就沒法得到鎖,只有阻塞等待。this
public void synSay() {
String x = "";
System.out.println("come in synSay----" + Thread.currentThread().getName());
synchronized (x) {
System.out.println("come in synchronized----" + Thread.currentThread().getName());
while (true) { //保證進入該方法的線程 一直佔用着該同步方法
}
}
}
public static void main(String[] args){
Test test1 = new Test();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test1.synSay();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000); //休眠3秒鐘 保證線程t1先執行
} catch (InterruptedException e) {
e.printStackTrace();
}
test1.synSay();
}
});
t1.start();
t2.start();
}
複製代碼
運行輸出
come in synSay----Thread-0
come in synchronized----Thread-0
come in synSay----Thread-1
複製代碼
能夠發現同步代碼塊和同步實例方法、同步類方法其實差很少,可是同步代碼塊將同步的範圍縮小了,能夠同步到指定的對象上,而不像同步實例方法、同步類方法那樣同步的是整個方法,因此同步代碼塊在效率上比其餘二者都有較大的提高。
須要注意的是,當同步代碼塊的時候,在類方法中加入同步代碼塊且同步的對象是xx.class
等類的引用的時候,同步的是該類,若是在****實例方法中加入同步代碼塊且同步的對象是this
,那麼同步的是該實例,能夠當作前者使用的是類的鎖**,後者使用的是實例的鎖。
建議把volatile
的特性和synchronized
的特性進行對比學習,加深理解。《Java volatile關鍵字解析》
JMM
關於synchronized
的兩條語義規定了:
大概流程:清空線程的工做內存->在主存中拷貝變量副本到工做內存->執行完畢->將變量副本寫回到主存中->釋放鎖。
因此synchronized
能保證共享變量的可見性,而實現這個流程的原理也是經過插入內存屏障,和關鍵字volatile
類似。
由於synchronized
是給共享變量加鎖,即便用阻塞的同步機制,共享變量只能同時被一個線程操做,因此JMM
不用像volatile
那樣考慮加內存屏障去保證synchronized
多線程狀況下的有序性,由於CPU
在單線程狀況下是保證了有序性的。
因此synchronized
修飾的代碼,是保證了有序性的。
一樣由於synchronized
是給共享變量加鎖了,以阻塞的機制去同步,在對共享變量進行讀/寫操做的時候是原子性的。
因此synchronized
修飾的代碼,是能保證原子性的。
Java併發編程的藝術
內存可見性和原子性:Synchronized和Volatile的比較
java synchronized類鎖,對象鎖詳解(轉載)
原文地址:ddnd.cn/2019/03/21/…