synchronized同步代碼塊java
用關鍵字synchronized聲明方法在某些狀況下是有弊端的,好比A線程調用同步方法執行一個較長時間的任務,那麼B線程必須等待比較長的時間。這種狀況下能夠嘗試使用synchronized同步語句塊來解決問題。看一下例子:安全
下面例子是優化後的例子 使用代碼塊鎖,原先例子是方法鎖,就是同步 必需要執行2個for bash
package org.java.base.sync;
public class ThreadDomain18
{
public void doLongTimeTask() throws Exception
{
for (int i = 0; i < 100; i++)
{
System.out.println("nosynchronized threadName = " +
Thread.currentThread().getName() + ", i = " + (i + 1));
}
System.out.println();
synchronized (this)
{
for (int i = 0; i < 100; i++)
{
System.out.println("synchronized threadName = " +
Thread.currentThread().getName() + ", i = " + (i + 1));
}
}
}
}複製代碼
package org.java.base.sync;
public class MyThread18 extends Thread
{
private ThreadDomain18 td;
public MyThread18(ThreadDomain18 td)
{
this.td = td;
}
public void run()
{
try
{
td.doLongTimeTask();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}複製代碼
package org.java.base.sync;
public class Test1 {
public static void main(String[] args)
{
ThreadDomain18 td = new ThreadDomain18();
MyThread18 mt0 = new MyThread18(td);
MyThread18 mt1 = new MyThread18(td);
mt0.start();
mt1.start();
}
}複製代碼
運行結果,分兩部分來看:多線程
synchronized threadName = Thread-1, i = 1
synchronized threadName = Thread-1, i = 2
nosynchronized threadName = Thread-0, i = 95
synchronized threadName = Thread-1, i = 3
nosynchronized threadName = Thread-0, i = 96
synchronized threadName = Thread-1, i = 4
nosynchronized threadName = Thread-0, i = 97
synchronized threadName = Thread-1, i = 5
nosynchronized threadName = Thread-0, i = 98
synchronized threadName = Thread-1, i = 6
nosynchronized threadName = Thread-0, i = 99
synchronized threadName = Thread-1, i = 7
nosynchronized threadName = Thread-0, i = 100複製代碼
...
synchronized threadName = Thread-1, i = 98
synchronized threadName = Thread-1, i = 99
synchronized threadName = Thread-1, i = 100
synchronized threadName = Thread-0, i = 1
synchronized threadName = Thread-0, i = 2
synchronized threadName = Thread-0, i = 3
...複製代碼
這個實驗能夠得出如下兩個結論:異步
一、當A線程訪問對象的synchronized代碼塊的時候,B線程依然能夠訪問對象方法中其他非synchronized塊的部分,第一部分的執行結果證實了這一點函數
二、當A線程進入對象的synchronized代碼塊的時候,B線程若是要訪問這段synchronized塊,那麼訪問將會被阻塞,第二部分的執行結果證實了這一點優化
因此,從執行效率的角度考慮,有時候咱們未必要把整個方法都加上synchronized,而是能夠採起synchronized塊的方式,對會引發線程安全問題的那一部分代碼進行synchronized就能夠了。ui
兩個synchronized塊之間具備互斥性this
若是線程1訪問了一個對象A方法的synchronized塊,那麼線程B對同一對象B方法的synchronized塊的訪問將被阻塞,寫個例子來證實一下:spa
public class ThreadDomain19
{
public void serviceMethodA()
{
synchronized (this)
{
try
{
System.out.println("A begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time = " + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public void serviceMethodB()
{
synchronized (this)
{
System.out.println("B begin time = " + System.currentTimeMillis());
System.out.println("B end time = " + System.currentTimeMillis());
}
}
}複製代碼
寫兩個線程分別調用這兩個方法:
public class MyThread19_0 extends Thread
{
private ThreadDomain19 td;
public MyThread19_0(ThreadDomain19 td)
{
this.td = td;
}
public void run()
{
td.serviceMethodA();
}
}複製代碼
public class MyThread19_1 extends Thread
{
private ThreadDomain19 td;
public MyThread19_1(ThreadDomain19 td)
{
this.td = td;
}
public void run()
{
td.serviceMethodB();
}
}複製代碼
寫個main函數:
public static void main(String[] args)
{
ThreadDomain19 td = new ThreadDomain19();
MyThread19_0 mt0 = new MyThread19_0(td);
MyThread19_1 mt1 = new MyThread19_1(td);
mt0.start();
mt1.start();
}複製代碼
看一下運行結果:
A begin time = 1443843271982
A end time = 1443843273983
B begin time = 1443843273983
B end time = 1443843273983複製代碼
看到對於serviceMethodB()方法synchronized塊的訪問必須等到對於serviceMethodA()方法synchronized塊的訪問結束以後。那其實這個例子,咱們也能夠得出一個結論:synchronized塊得到的是一個對象鎖,換句話說,synchronized塊鎖定的是整個對象。
synchronized塊和synchronized方法
既然上面獲得了一個結論synchronized塊得到的是對象鎖,那麼若是線程1訪問了一個對象方法A的synchronized塊,線程2對於同一對象同步方法B的訪問應該是會被阻塞的,由於線程2訪問同一對象的同步方法B的時候將會嘗試去獲取這個對象的對象鎖,但這個鎖卻在線程1這裏。寫一個例子證實一下這個結論:
public class ThreadDomain20
{
public synchronized void otherMethod()
{
System.out.println("----------run--otherMethod");
}
public void doLongTask()
{
synchronized (this)
{
for (int i = 0; i < 1000; i++)
{
System.out.println("synchronized threadName = " +
Thread.currentThread().getName() + ", i = " + (i + 1));
try
{
Thread.sleep(5);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}複製代碼
寫兩個線程分別調用這兩個方法:
public class MyThread20_0 extends Thread
{
private ThreadDomain20 td;
public MyThread20_0(ThreadDomain20 td)
{
this.td = td;
}
public void run()
{
td.doLongTask();
}
}複製代碼
public class MyThread20_1 extends Thread
{
private ThreadDomain20 td;
public MyThread20_1(ThreadDomain20 td)
{
this.td = td;
}
public void run()
{
td.otherMethod();
}
}複製代碼
寫個main函數調用一下,這裏」mt0.start()」後sleep(100)如下是爲了確保mt0線程先啓動:
public static void main(String[] args) throws Exception
{
ThreadDomain20 td = new ThreadDomain20();
MyThread20_0 mt0 = new MyThread20_0(td);
MyThread20_1 mt1 = new MyThread20_1(td);
mt0.start();
Thread.sleep(100);
mt1.start();
}複製代碼
看一下運行結果:
...
synchronized threadName = Thread-0, i = 995
synchronized threadName = Thread-0, i = 996
synchronized threadName = Thread-0, i = 997
synchronized threadName = Thread-0, i = 998
synchronized threadName = Thread-0, i = 999
synchronized threadName = Thread-0, i = 1000
----------run--otherMethod複製代碼
證實了咱們的結論。爲了進一步完善這個結論,把」otherMethod()」方法的synchronized去掉再看一下運行結果:
...
synchronized threadName = Thread-0, i = 16
synchronized threadName = Thread-0, i = 17
synchronized threadName = Thread-0, i = 18
synchronized threadName = Thread-0, i = 19
synchronized threadName = Thread-0, i = 20
----------run--otherMethod
synchronized threadName = Thread-0, i = 21
synchronized threadName = Thread-0, i = 22
synchronized threadName = Thread-0, i = 23
...複製代碼
「otherMethod()」方法和」doLongTask()」方法中的synchronized塊異步執行了
將任意對象做爲對象監視器
總結一下前面的內容:
一、synchronized同步方法
(1)對其餘synchronized同步方法或synchronized(this)同步代碼塊呈阻塞狀態
(2)同一時間只有一個線程能夠執行synchronized同步方法中的代碼
二、synchronized同步代碼塊
(1)對其餘synchronized同步方法或synchronized(this)同步代碼塊呈阻塞狀態
(2)同一時間只有一個線程能夠執行synchronized(this)同步代碼塊中的代碼
前面都使用synchronized(this)的格式來同步代碼塊,其實Java還支持對」任意對象」做爲對象監視器來實現同步的功能。這個」任意對象」大多數是實例變量及方法的參數,使用格式爲synchronized(非this對象)。看一下將任意對象做爲對象監視器的使用例子:
public class ThreadDomain21
{
private String userNameParam;
private String passwordParam;
private String anyString = new String();
public void setUserNamePassword(String userName, String password)
{
try
{
synchronized (anyString)
{
System.out.println("線程名稱爲:" + Thread.currentThread().getName() +
"在 " + System.currentTimeMillis() + " 進入同步代碼塊");
userNameParam = userName;
Thread.sleep(3000);
passwordParam = password;
System.out.println("線程名稱爲:" + Thread.currentThread().getName() +
"在 " + System.currentTimeMillis() + " 離開同步代碼塊");
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}複製代碼
寫兩個線程分別調用一下:
public class MyThread21_0 extends Thread
{
private ThreadDomain21 td;
public MyThread21_0(ThreadDomain21 td)
{
this.td = td;
}
public void run()
{
td.setUserNamePassword("A", "AA");
}
}複製代碼
public class MyThread21_1 extends Thread
{
private ThreadDomain21 td;
public MyThread21_1(ThreadDomain21 td)
{
this.td = td;
}
public void run()
{
td.setUserNamePassword("B", "B");
}
}複製代碼
寫一個main函數調用一下:
public static void main(String[] args)
{
ThreadDomain21 td = new ThreadDomain21();
MyThread21_0 mt0 = new MyThread21_0(td);
MyThread21_1 mt1 = new MyThread21_1(td);
mt0.start();
mt1.start();
}複製代碼
看一下運行結果:
線程名稱爲:Thread-0在 1443855101706 進入同步代碼塊
線程名稱爲:Thread-0在 1443855104708 離開同步代碼塊
線程名稱爲:Thread-1在 1443855104708 進入同步代碼塊
線程名稱爲:Thread-1在 1443855107708 離開同步代碼塊複製代碼
這個例子證實了:多個線程持有」對象監視器」爲同一個對象的前提下,同一時間只能有一個線程能夠執行synchronized(非this對象x)代碼塊中的代碼。
鎖非this對象具備必定的優勢:若是在一個類中有不少synchronized方法,這時雖然能實現同步,但會受到阻塞,從而影響效率。但若是同步代碼塊鎖的是非this對象,則synchronized(非this對象x)代碼塊中的程序與同步方法是異步的,不與其餘鎖this同步方法爭搶this鎖,大大提升了運行效率。
其實不管是方法所仍是代碼鎖都是要以一個對象監視器來鎖定,鎖定的代碼是同步的,鎖this是當前對象,鎖String是String這個對象,鎖Object是Object這個對象,互不干擾,若是有其它線程調用一樣用到跟上面鎖this、Objcet、String相同對象的方法或代碼,就須要等待同步,鎖代碼塊比鎖方法更加靈活。由於鎖方法鎖的是this 也就是當前對象,當一個線程正在調用當前這個對象的所方法時,致使其它線程調用不了該對象的其它鎖this的代碼,也調不了全部該對象的鎖方法
鎖的是當前這個線程,針對鎖的對象的這段代碼或方法,一次只能一個線程運行,其它線程運行到此的話會暫停,若是是執行其它非鎖的則是異步的,注意這裏不要被多線程搞迷糊了。單個線程執行的時候都是同步的,當這個線程被阻塞後,以後的代碼(鎖內的和鎖外的)不管什麼都不會執行,只有當喚醒或者恢復正常時纔會繼續往下走,走完鎖內的代碼就會放鎖,而後繼續走剩餘的代碼
注意一下」private String anyString = new String();」這句話,如今它是一個全局對象,所以監視的是同一個對象。若是移到try裏面,那麼對象的監視器就不是同一個了,調用的時候天然是異步調用,能夠本身試一下。
最後提一點,synchronized(非this對象x),這個對象若是是實例變量的話,指的是對象的引用,只要對象的引用不變,即便改變了對象的屬性,運行結果依然是同步的。