synchronized關鍵字可以添加在方法的聲明上,也可以添加在代碼塊中
添加在方法上時分兩種情況,當爲靜態方法時,表示的是對該類的.class對象上鎖
當不爲靜態方法時,表示的是對該類的對象上鎖。
添加在代碼塊時,需要指定上鎖的對象。
public class Synchonizedd {
static Long start,end;
static
{
start = System.currentTimeMillis();
}
//鎖定this對象
public synchronized void m1() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
end =System.currentTimeMillis();
System.out.println(end-start+" m1");
}
//鎖定Synchonizedd.class 對象
public static synchronized void m2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
end =System.currentTimeMillis();
System.out.println(end-start+" m2");
}
public void m3() {
//鎖定this對象
synchronized (this)
{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
end =System.currentTimeMillis();
System.out.println(end-start+" m3");
}
}
public static void main(String[] args) {
Synchonizedd synchonizedd = new Synchonizedd();
new Thread(()->synchonizedd.m1()).start();
new Thread(()->Synchonizedd.m2()).start();
new Thread(()->synchonizedd.m3()).start();
}
}
運行結果:
可以看到,因爲m1和m2方法鎖定的不是同一對象,所以調用m1方法的線程和調用m2方法的線程能夠並行執行。
而m1和m3中的synchronized代碼塊鎖定的是同一對象,調用兩個方法的線程不能並行運行,等到其中一個釋放該對象鎖之後另一個線程纔會運行。
synchnized 操作是原子操作,不可分。
一個synchnized 方法運行中,一個非同步方法是可以運行的
public class T2 {
static long start,end;
static
{
start = System.currentTimeMillis();
}
public synchronized void m1()
{
System.out.println(Thread.currentThread().getName()+" m1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
end=System.currentTimeMillis();
System.out.println("當前時間爲:"+(end-start) +" m1 to end");
}
public void m2()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
end=System.currentTimeMillis();
System.out.println("當前時間爲:"+(end-start)+" "+Thread.currentThread().getName()+" m2");
}
public static void main(String[] args) {
T2 t2 = new T2();
new Thread(()->t2.m1(),"Thread1").start();
new Thread(()->t2.m2(),"Thread2").start();
}
}
運行結果:
上述代碼中,我們開啓了兩個線程,一個線程執行m1方法,另一個執行m2方法,m1方法執行是需要獲得鎖的,m2方法不需要獲得鎖的,所以當m1執行的過程中,m2方法是可以同時執行的。
一個同步方法可以調用另一個同步方法,一個線程已經擁有某個對象的鎖,再次申請時仍然會得到該對象的鎖,也就是說synchronized獲得的鎖是可重入的。
public class T3 {
public synchronized void m1()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//執行同一個需要獲得鎖的方法
m2();
System.out.println(Thread.currentThread().getName()+" m1");
}
public synchronized void m2()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" m2");
}
public static void main(String[] args) {
T3 t3 = new T3();
new Thread(()->t3.m1(),"th1").start();
}
}
運行結果:
上述代碼中,我們開啓了一個線程,這個線程去執行m1方法,m1方法是需要獲得鎖的,m1方法中又需要調用m2方法,而m2方法也需要獲得鎖,這就形成了需要獲得T3類對象的兩次鎖,而實驗結果表明m2方法是可以執行的,這說明synchronized獲得的鎖是可重入的。
子類調用父類的同步方法,鎖定的是同一對象
public class T4 {
synchronized void m()
{
System.out.println("m start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("鎖定的對象是 "+this);
System.out.println("m end");
}
public static void main(String[] args) {
TT4 tt4 = new TT4();
new Thread(()->tt4.m(),"Thread").start();
}
}
class TT4 extends T4
{
public synchronized void m()
{
System.out.println("child m start");
super.m();
System.out.println("鎖定的對象是 "+this);
System.out.println("child m end");
}
}
運行結果:
通過運行結果發現,鎖定的確實是同一對象。
程序在執行過程中,如果發現異常,默認情況下鎖是會被釋放。
所以,在併發處理的過程中,有異常要多加小心,不然可能會發生不一致的情況。
比如,在一個web app處理過程中,多個servlet線程共同訪問一個資源,這時如果異常處理不合適,在一個線程中拋出異常,其他線程就會進入同步代碼區,有可能訪問到異常產生時的數據。
因此要非常小心的處理同步業務邏輯中的異常。
public class T5 {
int count = 0;
public synchronized void m()
{
while (true) {
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
//加上try catch塊則不會釋放鎖
/*try {
int i = 1 / 0;
}catch (Exception e)
{
e.printStackTrace();
}*/
//拋出異常,並釋放鎖
int i = 1 / 0;
}
System.out.println(Thread.currentThread().getName()+" count="+count);
}
}
public static void main(String[] args) {
T5 t5 = new T5();
new Thread(()->t5.m(),"t1").start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->t5.m(),"t2").start();
}
}
當不對異常進行處理時的運行結果(這裏截取出現異常後的運行結果):
當對異常進行trycatch處理時的運行結果:
上述代碼中,當拋出異常時,正在執行的方法會釋放鎖,而用trycatch處理之後,正在執行的方法不會釋放鎖。
synchronize 優化
同步代碼塊中的語句越少越好
public class T10 {
int count = 0;
synchronized void m1()
{
try {
Thread.sleep(2000);
for (int i=0;i<1000000;i++) count++;
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2()
{
try {
Thread.sleep(2000);
//業務邏輯中只有這句話需要sync,這時不應該給整個方法上鎖
//採用細粒度的鎖,可以使線程爭用時間變短,從而提高效率
synchronized (this)
{
for (int i=0;i<1000000;i++) count++;
}
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
鎖定某個對象o,如果o的屬性發生改變,不影響鎖的使用。
但是如果o引用另外一個對象,則鎖定的對象改變。
應避免將鎖定對象的引用變成另外的對象
public class T11 {
Object o = new Object();
void m1()
{
// 鎖定o引用的對象
synchronized (o) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("運行的線程:"+Thread.currentThread().getName() );
}
}
}
public static void main(String[] args) {
T11 t11 = new T11();
new Thread(()->t11.m1(),"t1").start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2= new Thread(()->t11.m1(),"t2");
// 改變引用對象
t11.o = new Object();
t2.start();
}
}
運行結果:
上述代碼中,先開啓線程t1,然後t1執行m1方法,之後改變o的引用,然後開啓一個線程t2,可以看到t1和t2同時運行,這是因爲t2和t1獲得的鎖的對象是不一樣的。
就像下面圖片一樣:
不要以字符串常量作爲鎖定對象。
在下面的例子中,m1和m2其實鎖定的是同一個對象。
這種情況還會發生比較隱退的現象,比如你用到一個類庫,在該類庫中代碼鎖定了字符串"Hello"。
但是你讀不到源碼,所以在自己的代碼中也鎖定了"Hello",這時候就有可能發生非常詭異的死鎖阻塞。
因爲你的程序和你用到的類庫不經意間使用了同一把鎖
public class T12 {
String s1 = "Hello";
String s2 = "Hello";
void m1()
{
synchronized (s1)
{
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("運行的線程:"+Thread.currentThread().getName());
}
}
}
void m2()
{
synchronized (s2)
{
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("運行的線程:"+Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T12 t12 = new T12();
new Thread(()->t12.m1(),"t1").start();
new Thread(()->t12.m2(),"t2").start();
}
}
運行結果:
使用Synchronized實現死鎖
死鎖形成的原理就是線程A需要線程B所佔用的鎖來執行程序,而線程B同時也需要線程A鎖佔用的鎖來執行程序,這就導致了兩個線程無休止的等待,等待對方釋放佔用的鎖。
public class DeadLock {
public static void main(String[] args) {
D1 d1 = new D1();
D2 d2 = new D2();
new Thread(()->d1.m1(d2),"Thread1").start();
new Thread(()->d2.m1(d1),"Thread2").start();
}
}
class D1
{
public synchronized void m1(D2 d2)
{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
d2.m2();
}
public synchronized void m2()
{
System.out.println("D1 m2");
}
}
class D2
{
public synchronized void m1(D1 d1)
{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
d1.m2();
}
public synchronized void m2()
{
System.out.println("D2 m2");
}
}
運行結果:
可見線程1和線程2都在等待對方釋放鎖,而自己需要等待對方釋放鎖之後才釋放自己的鎖,這就導致了死鎖的產生。