整理自博客:http://www.cnblogs.com/mengdd/archive/2013/02/16/2913806.html
0筒狠、“同步”的概述
多線程的同步機制是用來對資源進行加鎖的,使得在同一個時間省店,只有一個線程可以對某一對象進行操作粪小,同步用以解決多個線程同時訪問時可能出現(xiàn)的問題齐邦。
- 同步機制可以使用synchronized關(guān)鍵字實現(xiàn)锰蓬。
- 當synchronized關(guān)鍵字修飾一個方法的時候锣险,該方法就叫做同步方法旬昭。
- 當synchronized方法執(zhí)行完或發(fā)生異常時篙螟,會自動釋放鎖。
下面通過一個例子來對synchronized關(guān)鍵字的用法進行解析稳懒。
1闲擦、使用synchronized關(guān)鍵字與不使用synchronized關(guān)鍵字的區(qū)別
public class ThreadTest
{
public static void main(String[] args)
{
Example example = new Example();
Thread t1 = new Thread1(example);
Thread t2 = new Thread1(example);
t1.start();
t2.start();
}
}
class Example
{
public synchronized void execute()
{
for (int i = 0; i < 10; ++i)
{
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Hello: " + i);
}
}
}
class Thread1 extends Thread
{
private Example example;
public Thread1(Example example)
{
this.example = example;
}
@Override
public void run()
{
example.execute();
}
}
是否在execute()方法前加上synchronized關(guān)鍵字,這個例子程序的執(zhí)行結(jié)果會有很大的不同场梆。
如果不加synchronized關(guān)鍵字,則兩個線程同時執(zhí)行execute()方法纯路,輸出是兩組并發(fā)的或油。
如果加上synchronized關(guān)鍵字,則會先輸出一組0到9驰唬,然后再輸出下一組顶岸,說明兩個線程是順次執(zhí)行的腔彰。
2、多個方法同時使用synchronized關(guān)鍵字修飾
將程序改動一下辖佣,Example類中再加入一個方法execute2()霹抛。
之后再寫一個線程類Thread2,Thread2中的run()方法執(zhí)行的是execute2()卷谈。Example類中的兩個方法都是被synchronized關(guān)鍵字修飾的杯拐。
public class ThreadTest
{
public static void main(String[] args)
{
Example example = new Example();
Thread t1 = new Thread1(example);
Thread t2 = new Thread2(example);
t1.start();
t2.start();
}
}
class Example
{
public synchronized void execute()
{
for (int i = 0; i < 20; ++i)
{
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Hello: " + i);
}
}
public synchronized void execute2()
{
for (int i = 0; i < 20; ++i)
{
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("World: " + i);
}
}
}
class Thread1 extends Thread
{
private Example example;
public Thread1(Example example)
{
this.example = example;
}
@Override
public void run()
{
example.execute();
}
}
class Thread2 extends Thread
{
private Example example;
public Thread2(Example example)
{
this.example = example;
}
@Override
public void run()
{
example.execute2();
}
}
如果沒有synchronized關(guān)鍵字,則兩個方法并發(fā)執(zhí)行世蔗,并沒有相互影響端逼。
但是如例子程序中所寫,即便是兩個方法污淋,執(zhí)行結(jié)果永遠是執(zhí)行完一個線程的輸出再執(zhí)行另一個線程的顶滩。
這說明:如果一個對象有多個synchronized方法,某一時刻某個線程已經(jīng)進入到了某個synchronized方法寸爆,那么在該方法沒有執(zhí)行完畢前礁鲁,其他線程是無法訪問該對象的任何synchronized方法的。
結(jié)論:
當synchronized關(guān)鍵字修飾一個方法的時候赁豆,該方法叫做同步方法救氯。
Java中的每個對象都有一個鎖(lock),或者叫做監(jiān)視器(monitor)歌憨,當一個線程訪問某個對象的synchronized方法時着憨,將該對象上鎖,其他任何線程都無法再去訪問該對象的synchronized方法了(這里是指所有的同步方法务嫡,而不僅僅是同一個方法)甲抖,直到之前的那個線程執(zhí)行方法完畢后(或者是拋出了異常),才將該對象的鎖釋放掉心铃,其他線程才有可能再去訪問該對象的synchronized方法准谚。
注意這時候是給對象上鎖,如果是不同的對象去扣,則各個對象之間沒有限制關(guān)系柱衔。
嘗試在代碼中構(gòu)造第二個線程對象時傳入一個新的Example對象,則兩個線程的執(zhí)行之間沒有什么制約關(guān)系愉棱。
3.靜態(tài)方法被synchronized關(guān)鍵字修飾
當一個synchronized關(guān)鍵字修飾的方法同時又被static修飾唆铐,之前說過,非靜態(tài)的同步方法會將對象上鎖奔滑,但是靜態(tài)方法不屬于對象艾岂,而是屬于類,它會將這個方法所在的類的Class對象上鎖朋其。
一個類不管生成多少個對象王浴,它們所對應的是同一個Class對象脆炎。
public class ThreadTest
{
public static void main(String[] args)
{
Example example = new Example();
Thread t1 = new Thread1(example);
// 此處即便傳入不同的對象,靜態(tài)方法同步仍然不允許多個線程同時執(zhí)行
example = new Example();
Thread t2 = new Thread2(example);
t1.start();
t2.start();
}
}
class Example
{
public synchronized static void execute()
{
for (int i = 0; i < 20; ++i)
{
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Hello: " + i);
}
}
public synchronized static void execute2()
{
for (int i = 0; i < 20; ++i)
{
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("World: " + i);
}
}
}
class Thread1 extends Thread
{
private Example example;
public Thread1(Example example)
{
this.example = example;
}
@Override
public void run()
{
Example.execute();
}
}
class Thread2 extends Thread
{
private Example example;
public Thread2(Example example)
{
this.example = example;
}
@Override
public void run()
{
Example.execute2();
}
}
所以如果是靜態(tài)同步方法(execute()和execute2()都加上static關(guān)鍵字)氓辣,即便是向兩個線程傳入不同的Example對象秒裕,這兩個線程仍然是互相制約的,必須先執(zhí)行完一個钞啸,再執(zhí)行下一個几蜻。
結(jié)論:
如果某個synchronized方法是static的,那么當線程訪問該方法時爽撒,它鎖的并不是synchronized方法所在的對象入蛆,而是synchronized方法所在的類所對應的Class對象。Java中硕勿,無論一個類有多少個對象哨毁,這些對象會對應唯一一個Class對象,因此當線程分別訪問同一個類的兩個對象的兩個static源武,synchronized方法時扼褪,它們的執(zhí)行順序也是順序的,也就是說一個線程先去執(zhí)行方法粱栖,執(zhí)行完畢后另一個線程才開始话浇。
4. synchronized塊
synchronized塊寫法:
synchronized(object)
{
}
這個表示線程在執(zhí)行的時候會將object對象上鎖。(注意這個對象可以是任意類的對象闹究,也可以使用this關(guān)鍵字)幔崖。這樣就可以自行規(guī)定上鎖對象。
public class ThreadTest
{
public static void main(String[] args)
{
Example example = new Example();
Thread t1 = new Thread1(example);
Thread t2 = new Thread2(example);
t1.start();
t2.start();
}
}
class Example
{
private Object object = new Object();
public void execute()
{
synchronized (object)
{
for (int i = 0; i < 20; ++i)
{
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Hello: " + i);
}
}
}
public void execute2()
{
synchronized (object)
{
for (int i = 0; i < 20; ++i)
{
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("World: " + i);
}
}
}
}
class Thread1 extends Thread
{
private Example example;
public Thread1(Example example)
{
this.example = example;
}
@Override
public void run()
{
example.execute();
}
}
class Thread2 extends Thread
{
private Example example;
public Thread2(Example example)
{
this.example = example;
}
@Override
public void run()
{
example.execute2();
}
}
- synchronized方法實際上等同于用一個synchronized塊包住方法中的所有語句渣淤,然后在synchronized塊的括號中傳入this關(guān)鍵字赏寇。當然,如果是靜態(tài)方法价认,需要鎖定的則是class對象嗅定。
如下為鎖類與鎖對象的區(qū)別:
鎖整個類:注意lock對象是靜態(tài)的
public class TestThread {
private static Object lock=new Object(); //必須是靜態(tài)的。
public void execute(){
synchronized(lock){
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
}
鎖單個對象:
public class TestThread {
private Object lock=new Object();
public void execute(){
synchronized(lock){
//增加了個鎖用踩,鎖定了對象lock渠退,在同一個類實例中,是線程安全的脐彩,但不同的實例還是不安全的碎乃。 因為不同的實例有不同對象鎖lock
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
}
其實上面鎖定一個方法,等同于下面的:
public void execute(){
synchronized(this){ //同步的是當然對象
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
這里我們會有疑問丁屎,為啥有了synchronized關(guān)鍵字還要有這么一個synchronized塊荠锭,原因就是可能一個方法中我們只有幾行代碼會涉及到線程同步問題,所以synchronized塊比synchronized方法更加細粒度地控制了多個線程的訪問晨川,只有synchronized塊中的內(nèi)容不能同時被多個線程所訪問证九,方法中的其他語句仍然可以同時被多個線程所訪問(包括synchronized塊之前的和之后的)。
注意:被synchronized保護的數(shù)據(jù)應該是私有的共虑。
總結(jié):
synchronized方法是一種粗粒度的并發(fā)控制愧怜,某一時刻,只能有一個線程執(zhí)行該synchronized方法妈拌;
synchronized塊則是一種細粒度的并發(fā)控制拥坛,只會將塊中的代碼同步,位于方法內(nèi)尘分、synchronized塊之外的其他代碼是可以被多個線程同時訪問到的猜惋。