多線程基礎之synchronized和volatile

多線程安全三大特性:java

(1)原子性:安全

指一系列操做要麼一塊兒執行完成,要麼一塊兒不執行。例如i++操做其實並非原子的,線程須要先獲取到i的值而後在線程內存中對i的值進行+1再刷新到主內存中,在這個期間可能有別的線程對i的值進行了修改,這樣得出的結果就是錯誤的,因此咱們須要同步鎖Synchronized(Volatile並非原子性)。bash

(2)可見性:多線程

線程之間變量相互可見。假設有一個全局變量i的值爲0,同時有線程A和線程B對一個全局變量i執行++操做那麼在JMM(JAVA內存模型)中由A線程獲取全局變量i的值後在線程內存中對i執行++操做(因爲線程互相之間不可見此時對B線程來講i仍是0),此時B線程同時進行與A線程同樣的操做,在最後刷新到主內存中那麼i的值就爲1而不是2。這就是線程之間互相不可見形成的線程不安全。併發


                                                    線程安全狀況下





                                                    線程不安全狀況下
ide

(3)有序性:性能

即程序執行的順序按照代碼的前後順序執行。在Java內存模型中,容許編譯器和處理器對指令進行重排序,可是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。優化


synchronized:ui

java中自帶的同步鎖,可修飾方法或用來同步代碼塊
this

public class ThreadDemo implements Runnable {
    static int a=100;
	Object obj=new Object();
	@Override
	public void run() {
		test3();
	}
	/**
	 * Synchronzed修飾方法 此時至關於使用Synchronized(this){...}包裹了方法中的全部代碼
	 */
	public  synchronized void test1() {
		System.out.println();
	}
	
	/**
	 *  synchronized 代碼塊選擇一個對象做爲對象鎖 只有獲取到該對象鎖的線程才能夠訪問代碼塊
	 */
	public void test2() {
		synchronized(obj){
			System.out.println();
		}
	}
	
	/**
	 *  synchronized 修飾靜態方法 至關於synchronized(ThreadDemo.class) 以類字節碼文件做爲對象鎖  全部該類的對象想訪問代碼都必須獲取同一個鎖
	 */
	public synchronized static void test3() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			a--;
			System.out.println(Thread.currentThread().getName()+"----"+a);
		}
	}

} 複製代碼

1.修飾方法:

等於synchronized(this){...},一個對象實例中同時只能有一條線程訪問該方法,若是該對象實例中有其它修飾了synchronized的方法,那麼它們將共用一把鎖。


2.同步代碼塊:

語法:synchronized(obj){...},一個對象實例中同時只能有一條線程訪問該同步代碼塊,若是該對象實例中有其它使用了這個obj對象鎖修飾的代碼塊,那麼它們將共用一把鎖。


3.修飾靜態方法:

等於synchronized(xxx.class){...},全部該class的實例對象共用同一把鎖,以class字節碼文件做爲對象鎖。

代碼以下:

public class ThreadDemo implements Runnable {
    private static int a=0;

	@Override
	public void run() {
		add();
	}
   
	public static void main(String[] args) {
		Thread t1=new Thread(new ThreadDemo(),"線程1");
		Thread t2=new Thread(new ThreadDemo(),"線程2");
		t1.start();
		t2.start();
	}
	
	public synchronized static void add() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep(300); /**非線程安全狀況下讓線程休眠堆積 從新調度 產生竟態條件**/
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			a++;
			System.out.println(Thread.currentThread().getName()+":----"+a);
		}
		
	}

}複製代碼

運行結果:

線程1:----1
線程1:----2
線程1:----3
線程1:----4
……
線程1:----40
線程1:----41
線程1:----42
線程1:----43
線程1:----44
線程1:----45
線程1:----46
線程1:----47
線程1:----48
線程1:----49
線程1:----50
線程2:----51
線程2:----52
線程2:----53
線程2:----54
線程2:----55
線程2:----56
線程2:----57
線程2:----58
線程2:----59
……
線程2:----96
線程2:----97
線程2:----98
線程2:----99
線程2:----100
複製代碼

synchronized在使用時須要注意性能問題,應本身衡量好性能與線程安全之間的平衡。複製代碼

volatile:

java自帶的關鍵字,用來修飾變量,被聲明的變量對全部線程可見同時禁止重排序,但不保證原子性,假設有多條線程同時對變量值進行修改仍是會出現線程不安全。

修改上述代碼:

public class ThreadDemo implements Runnable {
    private volatile static int a=0;

	@Override
	public void run() {
		add();
	}
   
	public static void main(String[] args) {
		Thread t1=new Thread(new ThreadDemo(),"線程1");
		Thread t2=new Thread(new ThreadDemo(),"線程2");
		t1.start();
		t2.start();
	}
	
	public  static void add() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep(300); /**非線程安全狀況下讓線程休眠堆積 從新調度 產生竟態條件**/
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			}
			a++;
			System.out.println(Thread.currentThread().getName()+":----"+a);
		}
		
	}

}複製代碼

運行結果:

線程2:----1
線程1:----2
線程2:----4
線程1:----4
線程2:----5
線程1:----6
線程2:----7
線程1:----8
線程2:----9
線程1:----10
線程1:----11
線程2:----12
線程1:----13
線程2:----14
線程1:----16
線程2:----16
線程1:----18
線程2:----18
線程1:----19
線程2:----20
線程1:----21
線程2:----22
線程1:----23
線程2:----23
線程1:----24
線程2:----25
線程1:----26
線程2:----27
線程2:----29
線程1:----29
線程1:----30
線程2:----31
線程1:----32
線程2:----33
線程1:----34
線程2:----35
線程1:----36
線程2:----37
線程1:----38
線程2:----39
線程1:----40
線程2:----41
線程1:----42
線程2:----43
線程1:----44
線程2:----45
線程1:----46
線程2:----47
線程1:----48
線程2:----49
線程1:----50
線程2:----51
線程1:----52
線程2:----53
線程1:----54
線程2:----55
線程1:----56
線程2:----57
線程1:----58
線程2:----59
線程1:----60
線程2:----61
線程1:----62
線程2:----63
線程1:----64
線程2:----65
線程1:----66
線程2:----67
線程1:----68
線程2:----69
線程1:----70
線程2:----71
線程1:----72
線程2:----73
線程1:----74
線程2:----75
線程1:----76
線程2:----77
線程1:----78
線程2:----79
線程1:----80
線程2:----81
線程1:----82
線程2:----83
線程1:----84
線程2:----85
線程1:----86
線程2:----87
線程1:----88
線程2:----89
線程1:----90
線程2:----91
線程1:----92
線程2:----93
線程1:----94
線程2:----95
線程2:----97
線程1:----97
線程1:----98
線程2:----99複製代碼

線程1和線程2兩個線程仍是會得出相同的值,這就是由於同時有2個線程修改了值。


總結:
volatile只保證線程之間的可見性和禁止重排序,可是不能保證原子性。synchronized能夠保證原子性可是synchronized多了會影響性能,jdk1.6後對synchronized有了優化,如何使用須要看實際狀況本身衡量。

相關文章
相關標籤/搜索