1 第一章 併發程式設計基礎

1.3 Volatile及原子性

Volatile概念 Volatile關鍵字的主要作用是使變數在多個執行緒間可見。在多執行緒間可以進行變數的變更,使得執行緒間進行資料的共享可見。阻止指令重排序,happens-before

  • volatile的作用就是強制執行緒到主記憶體(共享記憶體)裡去讀取變數,而不去執行緒工作記憶體區裡去讀取,從而實現了多個執行緒間的變數可見。也就是滿足執行緒之間的可見性。

通過下面的原始碼及執行結果可以大概瞭解一下volatile的用法。


public class UseVolatile extends Thread {

	private volatile boolean isRunning = true;
	
	private void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}
	
	public void run(){
		System.out.println("進入run方法...");
		while(isRunning == true) {
			//....
		}
		System.err.println("執行緒停止!");
	}
	
	public static void main(String[] args) throws InterruptedException {
		UseVolatile uv = new UseVolatile();
		uv.start();
		Thread.sleep(2000);
		uv.setRunning(false); //修改isRunning = false
		System.out.println("isRunning的值已經被設定成了false!");
	}
}

在這裡插入圖片描述

Volatile記憶體模型如下圖: 在這裡插入圖片描述

Happens-before 如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須要存在happens-before關係,在這個例子就是A操作的結果要對B操作可見,那麼必然存在A happens-before B.

指令的重排序 JAVA語言為了維持順序內部的順序化語義,也就是為了保證程式的最終執行結果需要和在單執行緒嚴格意義的順序化環境下執行的結果一致,程式指令的執行順序有可能和程式碼的順序不一致,這個過程就稱之為指令的重排序。指令重排序的意義在於:JVM能根據處理器的特性,充分利用多級快取,多核等進行適當的指令重排序,使程式在保證業務執行的同時,充分利用CPU的執行特點,最大的發揮機器的效能!

Atomic Atomic系列類封裝了一系列的基礎型別和物件操作,其主要目的就是為了實現原子性,主要核心類如下:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReference

從下面的例子來看看保證原子性的必要性。

import java.util.ArrayList;
import java.util.List;

public class UseAtomicInteger {

	private static int count=0;
	
	public synchronized int add() {
		count += 10;
		return count;
	}
	
	public static void main(String[] args) {
		List<Thread> list = new ArrayList<>();
		for (int i = 0; i < 20; i++) {
			list.add(new Thread( new Runnable() {
				@Override
				public void run() {
					System.err.println("累計結果:" + (new UseAtomicInteger()).add());
				} 
			})); 
		}
		for (Thread thread : list) {
			thread.start();
		}
	}
}

多執行幾次後,會發現輸出為10(或者別的數字)的有2條,輸出為200的沒有。 在這裡插入圖片描述

為了保證原子性,採用AtomicInteger來替代普通的int型別。下面是改進後的程式碼

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class UseAtomic {

	private static AtomicInteger count = new AtomicInteger(0);
	
	public synchronized int add() {
		count.addAndGet(10);
		return count.get();
	}
	
	public static void main(String[] args) {
		UseAtomic ua = new UseAtomic();
		
		List<Thread> list = new ArrayList<>();
		for (int i = 0; i < 20; i++) {
			list.add(new Thread( new Runnable() {
				
				@Override
				public void run() {
					System.err.println("累計結果:" + ua.add());
				} 
			})); 
		}
		
		for (Thread thread : list) {
			thread.start();
		}
	}
}

這裡是輸出結果: 在這裡插入圖片描述

如果操作物件不是簡單資料型別,而是物件型別,可以使用AtomicReference。 下面給出一個例子。

import java.util.concurrent.atomic.AtomicReference;

public class UseAtomicReference {
	private static Person person; 
	
	private static AtomicReference<Person> personRfer;
	
	public static void main(String[] args) throws InterruptedException {
		person = new Person("Tom", 18);
		personRfer = new  AtomicReference<Person>(person);
		System.out.println("Person is " + personRfer.get());
		
		Thread t1 = new Thread(new Task1());
		Thread t2 = new Thread(new Task2());
		
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		
		Thread.sleep(100);
		System.out.println("Now Person is " + personRfer.get());
	}

	static class Task1 implements Runnable {
		public void run() {
			Person person=new Person("Rose",19);
	 		personRfer.getAndSet(person);
			System.out.println("Thread1 Values " + personRfer.get());
		}
	}

	static class Task2 implements Runnable {
		public void run() {
			Person person=new Person("Sophia",20);
			personRfer.getAndSet(person);
			System.out.println("Thread2 Values " + personRfer.get());
		}
	}
}