什么是Synchronized,有什么用?
Java語言的關(guān)鍵字之剧,當(dāng)它用來修飾一個(gè)方法或者一個(gè)代碼塊的時(shí)候牌芋,能夠保證在同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼迂猴。
使用方式
1. 修飾對(duì)象方法(也就是非靜態(tài)方法)
如果一個(gè)線程正在訪問這個(gè)對(duì)象中synchronized修飾的方法時(shí)佑菩,那么其他試圖訪問
該對(duì)象
中任何
synchronized修飾的方法的線程都將被阻塞.
很抽象,沒關(guān)系,繼續(xù)往下.
/**
* 對(duì)象方法加synchronized關(guān)鍵字
*/
class SyncDemo {
synchronized void exec1() {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);//省略try_catch
System.out.println("exec1-->" + Thread.currentThread().getName() + "-->" + i);
}
}
synchronized void exec2() {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);//省略try_catch
System.out.println("exec2-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
同一個(gè)對(duì)象,同一個(gè)方法
public class SyncDemoTest {
public static void main(String[] args) {
//先將對(duì)象實(shí)例化,兩個(gè)線程用的是一個(gè)對(duì)象
final SyncDemo syncDemo = new SyncDemo();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec1();
}
}, "thread2").start();
}
}
運(yùn)行結(jié)果
exec1-->thread1-->0
exec1-->thread1-->1
exec1-->thread1-->2
exec1-->thread1-->3
exec1-->thread1-->4
exec1-->thread2-->0
exec1-->thread2-->1
exec1-->thread2-->2
exec1-->thread2-->3
exec1-->thread2-->4
同一個(gè)對(duì)象,不同方法
public class SyncDemoTest {
public static void main(String[] args) {
//先將對(duì)象實(shí)例化,兩個(gè)線程用的是一個(gè)對(duì)象
final SyncDemo syncDemo = new SyncDemo();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo1.exec2();
}
}, "thread2").start();
}
}
運(yùn)行結(jié)果
exec1-->thread1-->0
exec1-->thread1-->1
exec1-->thread1-->2
exec1-->thread1-->3
exec1-->thread1-->4
exec2-->thread2-->0
exec2-->thread2-->1
exec2-->thread2-->2
exec2-->thread2-->3
exec2-->thread2-->4
從結(jié)果中我們會(huì)發(fā)現(xiàn),當(dāng)exec1()執(zhí)行的時(shí)候,exec2()必須要等待exec1()執(zhí)行完成后才會(huì)執(zhí)行.
說明: 如果一個(gè)對(duì)象有多個(gè)synchronized方法扁凛,某一時(shí)刻某個(gè)線程已經(jīng)進(jìn)入到了某個(gè)synchronized方法忍疾,那么在該方法沒有執(zhí)行完畢前,其他線程是無法訪問該對(duì)象的任何synchronized方法的谨朝。
結(jié)論
當(dāng)synchronized關(guān)鍵字修飾一個(gè)方法的時(shí)候卤妒,該方法叫做同步方法甥绿。
Java中的每個(gè)對(duì)象都有一個(gè)鎖(lock),或者叫做監(jiān)視器(monitor)则披,當(dāng)一個(gè)線程訪問某個(gè)對(duì)象的synchronized方法時(shí)共缕,將該對(duì)象上鎖,其他任何線程都無法再去訪問該對(duì)象的synchronized方法了(這里是指所有的同步方法士复,而不僅僅是同一個(gè)方法)图谷,直到之前的那個(gè)線程執(zhí)行方法完畢后(或者是拋出了異常),才將該對(duì)象的鎖釋放掉阱洪,其他線程才有可能再去訪問該對(duì)象的synchronized方法便贵。
注意這時(shí)候是給對(duì)象上鎖,如果是不同的對(duì)象冗荸,則各個(gè)對(duì)象之間沒有限制關(guān)系承璃。
one more thing
代碼中構(gòu)造兩個(gè)線程時(shí),分別在run方法里傳入不同的SyncDemo1對(duì)象,那么兩個(gè)線程的執(zhí)行之間將沒有什么制約關(guān)系蚌本。
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec2();
}
}, "thread2").start();
}
2. 修飾靜態(tài)方法
當(dāng)一個(gè)synchronized關(guān)鍵字修飾的方法同時(shí)又被static修飾盔粹,之前說過,非靜態(tài)的同步方法會(huì)將對(duì)象上鎖程癌,但是靜態(tài)方法不屬于對(duì)象舷嗡,而是屬于類,它會(huì)將這個(gè)方法所在的類的Class對(duì)象上鎖席楚。
一個(gè)類不管生成多少個(gè)對(duì)象咬崔,它們所對(duì)應(yīng)的是同一個(gè)Class對(duì)象。
/**
* 靜態(tài)方法加synchronized關(guān)鍵字
*/
class SyncDemo {
//加入靜態(tài)修飾
synchronized static void exec1() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec1-->" + Thread.currentThread().getName() + "-->" + i);
}
}
//加入靜態(tài)修飾
synchronized static void exec2() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec2-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
public class SyncDemoTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SyncDemo.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
SyncDemo.exec2();
}
}, "thread2").start();
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec1();
}
}, "thread3").start();
new Thread(new Runnable() {
@Override
public void run() {
new SyncDemo().exec2();
}
}, "thread4").start();
}
}
運(yùn)行結(jié)果
exec1-->thread1-->0
exec1-->thread1-->1
exec1-->thread1-->2
exec1-->thread1-->3
exec1-->thread1-->4
exec2-->thread4-->0
exec2-->thread4-->1
exec2-->thread4-->2
exec2-->thread4-->3
exec2-->thread4-->4
exec1-->thread3-->0
exec1-->thread3-->1
exec1-->thread3-->2
exec1-->thread3-->3
exec1-->thread3-->4
exec2-->thread2-->0
exec2-->thread2-->1
exec2-->thread2-->2
exec2-->thread2-->3
exec2-->thread2-->4
從結(jié)果中我們會(huì)發(fā)現(xiàn),4個(gè)線程的調(diào)用時(shí)依次執(zhí)行的
如果某個(gè)synchronized方法是static的烦秩,那么當(dāng)線程訪問該方法時(shí)垮斯,它鎖的并不是synchronized方法所在的對(duì)象,而是synchronized方法所在的類所對(duì)應(yīng)的Class對(duì)象只祠。Java中兜蠕,無論一個(gè)類有多少個(gè)對(duì)象,這些對(duì)象會(huì)對(duì)應(yīng)唯一一個(gè)Class對(duì)象抛寝,因此當(dāng)線程分別訪問同一個(gè)類的兩個(gè)對(duì)象的兩個(gè)static熊杨,synchronized方法時(shí),它們的執(zhí)行順序也是順序的盗舰,也就是說一個(gè)線程先去執(zhí)行方法晶府,執(zhí)行完畢后另一個(gè)線程才開始
3. 修飾代碼塊
如下,
對(duì)象鎖
void exec() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println("exec-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
其效果等同于修飾對(duì)象方法.this作為對(duì)象鎖.
類鎖
static void exec() {
synchronized (SyncDemo.class) {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println("exec-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
其效果等同于修飾靜態(tài)方法.
4. 混合修飾
class SyncDemo {
synchronized void exec() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec -->" + Thread.currentThread().getName() + "-->" + i);
}
}
synchronized static void exec1() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec1-->" + Thread.currentThread().getName() + "-->" + i);
}
}
synchronized static void exec2() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("exec2-->" + Thread.currentThread().getName() + "-->" + i);
}
}
}
public class SyncDemoTest {
public static void main(String[] args) {
final SyncDemo syncDemo = new SyncDemo();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo.exec();
}
}, "thread ").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo.exec1();
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
syncDemo.exec2();
}
}, "thread2").start();
}
}
運(yùn)行結(jié)果
exec -->thread -->0
exec1-->thread1-->0
exec -->thread -->1
exec1-->thread1-->1
exec -->thread -->2
exec1-->thread1-->2
exec -->thread -->3
exec1-->thread1-->3
exec -->thread -->4
exec1-->thread1-->4
exec2-->thread2-->0
exec2-->thread2-->1
exec2-->thread2-->2
exec2-->thread2-->3
exec2-->thread2-->4
從結(jié)果來看,對(duì)象鎖的thread和類鎖的thread1 兩個(gè)線程交替進(jìn)行.
說明對(duì)象鎖跟類的鎖之間沒有相互制約關(guān)系
Tips
-
synchronized方法與synchronized代碼塊的區(qū)別
synchronized methods(){} 與synchronized(this){}之間沒有什么區(qū)別,只是 synchronized methods(){} 便于閱讀理解钻趋,而synchronized(this){}可以更精確的控制沖突限制訪問區(qū)域川陆,有時(shí)候表現(xiàn)更高效率
-
synchronized關(guān)鍵字是不能繼承的
-
使用synchronized關(guān)鍵字解決線程的同步問題會(huì)帶來一些執(zhí)行效率上的問題。
JDK 5.0引入了這樣一個(gè)包:java.util.concurrent:
簡(jiǎn)述
無論synchronized關(guān)鍵字加在方法上還是對(duì)象上蛮位,如果它作用的對(duì)象是非靜態(tài)的较沪,則它取得的鎖是對(duì)象鳞绕;如果synchronized作用的對(duì)象是一個(gè)靜態(tài)方法或一個(gè)類,則它取得的鎖是類.
每個(gè)對(duì)象只有一個(gè)鎖(lock)與之相關(guān)聯(lián)尸曼,誰拿到這個(gè)鎖誰就可以運(yùn)行它所控制的那段代碼们何。
類的鎖跟對(duì)象的鎖之間沒有制約關(guān)系.