Java同步原語synchronized、volatile

synchronized

通常的多線程,執行順序隨機:bash

public class ThreadTest {
 
	public static void main(String[] args)
	{
		for(int i=0;i<3;i++)
		{
			MyThread temp=new MyThread();
			temp.start();
		}
	}
}
 
class MyThread extends Thread
{
	@Override
	public void run()
	{
		for(int i=0;i<5;i++)
		{
			System.out.println(Thread.currentThread().getName()+": ["+i+"]");
		}
	}
}
複製代碼

執行效果:多線程

使用synchronized關鍵字修飾的多線程會順序執行:app

package 同步;

public class SynchronizedTest {

	static int number=0;
	//synchronized塊(對象級)
	public String setNumber1()
	{
		 synchronized(this)
		 {
			number++;			
			return "setNumber1.:"+number;
		 }			
	}
	
	//synchronized塊(類級別)
	public String setNumber2()
	{
		 synchronized(SynchronizedTest.class)
		 {
			number++;			
			return "setNumber2.:"+number;
		 }		
	}
	
	//synchronized 方法
	public synchronized String setNumber3()
	{		
		number++;			
		return "setnumber3.:"+number; 		
	}
	public String setNumber4() {
		return"setnumber4.:"+number;
	}
}

package 同步;
public class Test{
	public static void main(String[] args)
	{
		SynchronizedTest t = new SynchronizedTest();
		for(int i=0;i<3;i++)
		{
			MyThread2 temp=new MyThread2(t);
			temp.start();
		}
	}
}
class MyThread2 extends Thread{
	SynchronizedTest Object;
	public MyThread2(SynchronizedTest Object) {
		this.Object = Object;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
	  try {
		Thread.sleep(0);
	} catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
	  System.out.println(Thread.currentThread().getName()+"--"+Object.setNumber1());
	  System.out.println(Thread.currentThread().getName()+"--"+Object.setNumber3());
	}
	
}
複製代碼

執行效果:ide

因此使用鎖進行多線程的同步就能控制執行的順序。ui

volatile

volatile的讀寫操做與鎖的釋放和獲取有着一樣的效果this

public class VolatileTest {
    volatile int number = 0;

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}
    public int getandIncrement() {
    	number++;
    	return number;
    }
    public static void main(String args[]) {
 	   VolatileTest test = new VolatileTest();
 	   test.setNumber(5);
 	   System.out.println( test.getNumber());
 	 test.getandIncrement();
 	  System.out.println(test.getandIncrement());
    }
    
}
複製代碼

運行結果:spa

package 同步;

public class VolatileExample {
   int number = 0;
   public synchronized void setNumber(int number) {
	  this.number = number;
   }
   public int getandIncrement() {
   	int t =getNumber();
   	t+= 1;
   setNumber(t);
   return t;
   }
   public synchronized int getNumber() {
		return number;
	}
   public static void main(String args[]) {
	   VolatileExample test = new VolatileExample();
	   test.setNumber(5);
	   System.out.println( test.getNumber());
	 test.getandIncrement();
	  System.out.println(test.getandIncrement());
   }
}
複製代碼

運行結果:線程

從上面的例子中能夠看出對一個 volatile變量的單個讀/寫操做,與對一個普通變量的讀/寫操做使用同一個鎖來同步,它們之間的執行效果相同。鎖的happens-before規則保證釋放鎖和獲取鎖的兩個線程之間的內存可見性,這意味着對一個volatile變量的讀,老是能看到(任意線程)對這個 volatile變量最後的寫入。鎖的語義決定了臨界區代碼的執行具備原子性。這意味着即便是 64 位的 long 型和double 型變量,只要它是volatile變量,對該變量的讀寫就將具備原子性。若是是多個volatile操做或相似於 volatile++這種複合操做,這些操做總體上不具備原子性。簡而言之,volatile 變量自身具備下列特性:code

 可見性。對一個 volatile 變量的讀,老是能看到(任意線程)對這個 volatile 變量最後的寫入。cdn

 原子性:對任意單個 volatile 變量的讀/寫具備原子性,但相似於 volatile++這 種複合操做不具備原子性

volatile 寫-讀創建的 happens before 關係

從內存語義的角度來講,volatile 的寫-讀與鎖的釋放-獲取有相同的內存效果: volatile 寫和鎖的釋放有相同的內存語義;volatile 讀與鎖的獲取有相同的內存語 義。 請看下面使用 volatile 變量的示例代碼:

class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; //1
flag = true; //2
}
public void reader() {
if (flag) { //3
int i = a; //4
}
}
}
複製代碼

假設線程 A 執行 writer()方法以後,線程 B 執行 reader()方法。根據 happens before 規則,這個過程創建的 happens before 關係能夠分爲兩類:

  1. 根據程序次序規則,1 happens before 2; 3 happens before 4。
  2. 根據 volatile 規則,2 happens before 3。
  3. 根據 happens before 的傳遞性規則,1 happens before 4。 上述 happens before 關係的圖形化表現形式以下: 在上圖中,每個箭頭連接的兩個節點,表明了一個 happens before 關係。黑色 箭頭表示程序順序規則;橙色箭頭表示 volatile 規則;藍色箭頭表示組合這些規則 後提供的 happens before 保證。 這裏 A 線程寫一個 volatile 變量後,B 線程讀同一個 volatile 變量。A 線程在寫 volatile 變量以前全部可見的共享變量,在 B 線程讀同一個 volatile 變量後,將立 即變得對 B 線程可見。 volatile讀寫時就是幾個線程經過主內存進行通訊

volatile 寫-讀的內存語義 volatile 寫的內存語義以下: .當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量值刷 新到主內存。 以上面示例程序 VolatileExample 爲例,假設線程 A 首先執行 writer()方法,隨後 線程 B 執行 reader()方法,初始時兩個線程的本地內存中的 flag 和 a 都是初始狀 態。

線程 A 在寫 flag 變量後,本地內存 A 中被線程 A 更新過的兩個共享變量的值被刷新到主內存中。此時,本地內存 A 和主內存中的共享變量的值是一致的。

volatile 讀的內存語義以下:

當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。

在讀 flag 變量後,本地內存 B 包含的值已經被置爲無效。此時,線程 B 必須從主內存中讀取共享變量。線程 B 的讀取操做將致使本地內存 B 與主內存中的共享變量的值也變成一致的了。若是咱們把 volatile 寫和 volatile 讀這兩個步驟綜合起來看的話,在讀線程 B讀一個volatile變量後,寫線程A在寫這個volatile變量以前全部可見的共享變量的值 都將當即變得對讀線程 B 可見。下面對 volatile 寫和 volatile 讀的內存語義作個總結:

線程 A 寫一個 volatile 變量,實質上是線程A向接下來將要讀這個volatile變量的某個線程發出了(其對共享變量所在修改的)消息。

線程 B 讀一個 volatile 變量,實質上是線程B接收了以前某個線程發出的(在寫這個volatile變量以前對共享變量所作修改的)消息。

線程 A 寫一個 volatile 變量,隨後線程 B 讀這個 volatile 變量,這個過程實質上是線程 A 經過主內存向線程 B發送消息。

相關文章
相關標籤/搜索