java多線程總結-同步之synchronized關鍵字

1.爲何要使用synchronized?

在併發編程中存在線程安全問題,主要緣由有:
1.存在共享數據
2.多線程共同操做共享數據。關鍵字synchronized能夠保證在同一時刻,只有一個線程能夠執行某個方法或某個代碼塊,同時synchronized能夠保證一個線程的變化可見(可見性)java

2.synchronized鎖什麼,加鎖的目的是什麼?

synchronized鎖的是對象,鎖的的對象多是this、臨界資源對象、class類對象
加鎖的目的是保證操做的原子性編程

3.代碼示例

3.1鎖this和臨界資源對象

本例中:安全

  • testSync1是synchronized代碼塊,鎖的是object,臨界資源對象
  • testSync2和testSync3都是鎖的是當前對象this
/**
 * synchronized關鍵字
 * 鎖對象。synchronized(this)和synchronized方法都是鎖當前對象。
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_01 {
	private int count = 0;
	private Object object = new Object();

	// 1.synchronized代碼塊,鎖的是object,臨界資源對象
	public void testSync1(){
		synchronized(object){
			System.out.println(Thread.currentThread().getName() 
					+ " count = " + count++);
		}
	}

	// 2.synchronized代碼塊,鎖的是當前對象this
	public void testSync2(){
		synchronized(this){
			System.out.println(Thread.currentThread().getName() 
					+ " count = " + count++);
		}
	}

	// 2.synchronized代碼,鎖的也是this
 	public synchronized void testSync3(){
		System.out.println(Thread.currentThread().getName() 
				+ " count = " + count++);
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		final Test_01 t = new Test_01();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.testSync3();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.testSync3();
			}
		}).start();
	}
	
}

複製代碼

3.2鎖class類對象

/**
 * synchronized關鍵字
 * 同步方法 - static
 * 靜態同步方法,鎖的是當前類型的類對象。在本代碼中就是Test_02.class
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_02 {
	private static int staticCount = 0;
	
	public static synchronized void testSync4(){
		System.out.println(Thread.currentThread().getName() 
				+ " staticCount = " + staticCount++);
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void testSync5(){
		synchronized(Test_02.class){
			System.out.println(Thread.currentThread().getName() 
					+ " staticCount = " + staticCount++);
		}
	}
	
}

複製代碼

3.3 何時鎖臨界資源,何時鎖當前對象?

若是在加鎖的時候須要對當前對象的訪問限定,建議鎖臨界資源(即鎖一個臨界資源),若是對當前鎖級別比較高的話,就鎖當前對象。建議都以同步代碼塊的方式進行開發,這樣能夠避免鎖的範圍過高bash

同步方法和非同步方法 提問:同步方法是否影響其餘線程調用非同步方法,或調用其餘鎖資源的同步方法?多線程

代碼示例 m1是非同步方法,m2是同步方法,m3同步代碼塊,鎖的臨界資源,這段代碼的目的是爲了證實在調用同步方法m1時,m2,m3是否可以執行併發

/**
 * synchronized關鍵字
 * 同步方法 - 同步方法和非同步方法的調用
 * 同步方法隻影響鎖定同一個鎖對象的同步方法。不影響其餘線程調用非同步方法,或調用其餘鎖資源的同步方法。
 */
package com.bernardlowe.concurrent.t01;

public class Test_04 {
	Object o = new Object();
	public synchronized void m1(){ // 重量級的訪問操做。
		System.out.println("public synchronized void m1() start");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("public synchronized void m1() end");
	}
	
	public void m3(){
		synchronized(o){
			System.out.println("public void m3() start");
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("public void m3() end");
		}
	}
	
	public void m2(){
		System.out.println("public void m2() start");
		try {
			Thread.sleep(1500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("public void m2() end");
	}
	
	public static class MyThread01 implements Runnable{
		public MyThread01(int i, Test_04 t){
			this.i = i;
			this.t = t;
		}
		int i ;
		Test_04 t;
		public void run(){
			if(i == 0){
				t.m1();
			}else if (i > 0){
				t.m2();
			}else {
				t.m3();
			}
		}
	}
	
	public static void main(String[] args) {
		Test_04 t = new Test_04();
		new Thread(new MyThread01(0, t)).start();
		new Thread(new MyThread01(1, t)).start();
		new Thread(new MyThread01(-1, t)).start();
	}
	
}

複製代碼

執行結果 ide

能夠看出,在執行同步方法m1時,m2,m3不受影響,依然能夠執行
結論:同步方法隻影響鎖定同一個鎖對象的同步方法。不影響其餘線程調用非同步方法,或調用其餘鎖資源的同步方法。
但有一點須要注意,儘可能在商業開發中避免同步方法。使用同步代碼塊。 細粒度解決同步問題。

3.3 重入鎖

這裏重入鎖分爲兩類:this

  • 1.在同步方法裏面調用其餘同步方法
  • 2.子類同步方法覆蓋父類同步方法

下面來看第一種:在同步方法裏面調用其餘同步方法
思考:調用m1()方法,m2()方法是否會執行?spa

/**
 *synchronized關鍵字
 *同步方法 - 調用其餘同步方法
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_06 {
	
	synchronized void m1(){ // 鎖this
		System.out.println("m1 start");
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		m2();
		System.out.println("m1 end");
	}
	synchronized void m2(){ // 鎖this
		System.out.println("m2 start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m2 end");
	}
	
	public static void main(String[] args) {
		
		new Test_06().m1();
		
	}
	
}  
複製代碼

結果:在m1()方法調用時,m2()方法依然可執行。屢次調用同步代碼,鎖定同一個鎖對象,是可重入的

第二種狀況:子類同步方法覆蓋父類同步方法
思考:子類同步方法m()中,調用父類同步方法m(),是否可重入?線程

/**
 * synchronized關鍵字
 * 同步方法 - 繼承
 * 子類同步方法覆蓋父類同步方法。能夠指定調用父類的同步方法。
 * 至關於鎖的重入。
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_07 {
	
	synchronized void m(){
		System.out.println("Super Class m start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("Super Class m end");
	}
	
	public static void main(String[] args) {
		new Sub_Test_07().m();
	}
	
}

class Sub_Test_07 extends Test_07{
	synchronized void m(){
		System.out.println("Sub Class m start");
		super.m();
		System.out.println("Sub Class m end");
	}
}

複製代碼

結果:子類同步方法m()中,調用了父類同步方法m(),能夠重入

3.4 鎖與異常

思考:當同步方法或同步代碼塊中發生異常,是否會影響其餘線程的執行?
下面來看一段代碼

/**
 * synchronized關鍵字
 * 同步方法 - 鎖與異常
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_08 {
	int i = 0;
	synchronized void m(){
		System.out.println(Thread.currentThread().getName() + " - start");
		while(true){
			i++;
			System.out.println(Thread.currentThread().getName() + " - " + i);
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// 當i==5時會拋出異常
			if(i == 5){
				i = 1/0;
			}
		}
	}
	
	public static void main(String[] args) {
		final Test_08 t = new Test_08();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "t1").start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "t2").start();
	}
	
}

複製代碼

這段代碼中先運行了兩個線程t一、t2,當其中一個線程發生異常時,另一個線程是否能繼續執行

結果:當同步方法中發生異常的時候,自動釋放鎖資源。不會影響其餘線程的執行。

思考: 同步業務邏輯中,若是發生異常如何處理?
好比上面會發生異常的代碼中,能夠這樣

if(i == 5){
				try {
					i = 1/0;
				} catch (Exception e) {
					i = 0;
				}
			}
複製代碼

3.5 鎖對象變動問題

代碼示例:8 思考:當一個線程執行同步方法時,另外一個線程修改了鎖對象,是否還能執行同步代碼塊

/**
 * synchronized關鍵字
 * 鎖對象變動問題
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_13 {
	Object o = new Object();
	
	void m(){
		System.out.println(Thread.currentThread().getName() + " start");
		synchronized (o) {
			while(true){
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + " - " + o);
			}
		}
	}
	
	public static void main(String[] args) {
		final Test_13 t = new Test_13();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "thread1").start();
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "thread2");
		t.o = new Object();
		thread2.start();
	}
	
}

複製代碼

結論:能夠看出,其餘線程依然能夠執行同步方法
由於同步代碼一旦加鎖後,那麼會有一個臨時的鎖引用執行鎖對象,和真實的引用無直接關聯。在鎖未釋放以前,修改鎖對象引用,不會影響同步代碼的執行。

注意:不要使用靜態常量做爲鎖對象
以下代碼,由於String常量池的問題,s1,s1是同一個對象,因此m1,m2方法鎖的是也同一個對象,m1同步方法被執行後,m2方法不會被執行

/**
 * synchronized關鍵字
 * 常量問題
 */
package com.bernardlowe.concurrent.t01;

public class Test_14 {
	String s1 = "hello";
	String s2 = "hello";
	// String s2 = new String("hello"); // new關鍵字,必定是在堆中建立一個新的對象。
	void m1(){
		synchronized (s1) {
			System.out.println("m1()");
			while(true){
				
			}
		}
	}
	
	void m2(){
		synchronized (s2) {
			System.out.println("m2()");
			while(true){
				
			}
		}
	}
	
	public static void main(String[] args) {
		final Test_14 t = new Test_14();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m1();
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m2();
			}
		}).start();
	}
	
}
複製代碼
相關文章
相關標籤/搜索