Java多線程以內存可見性

Java內存模型( JMM ) :html

    1) 全部的變量都存儲在主內存中java

    2) 每一個線程都有本身獨立的工做內存, 裏面保存該線程使用到的變量的副本 ( 主內存中該變量的一份拷貝 )ide

JMM 兩條規定:ui

    1) 線程對共享變量的全部操做都必須在本身的工做內存中進行spa

    2) 不一樣線程之間沒法直接訪問其餘線程工做內存中的共享變量, 線程間共享變量值的傳遞必須經過主內存 .net

線程間共享變量可見性實現的原理:線程

    線程A 對共享變量的修改想被線程B 及時看到, 必需要通過如下2個步驟:code

        1) 把線程A 工做內存中更新過的共享變量刷新到主內存中( store )htm

        2) 將主內存中最新的共享變量的值共享到線程B 工做內存中( load )blog

 

Java 語言層面支持的可見性實現方式:

    1) synchronized

    2) volatile

JUC 包下的類也能夠實現可見性

    1) Atomic

    2) ReentrantLock

    3) Semaphore

 

1. synchronized 實現可見性

    JMM 關於 synchronized 的兩條規定:

        1) 線程釋放鎖前, 必須把共享變量的最新值從該線程的工做內存刷新到主內存中

        2) 線程持有鎖時, 將清空該線程工做內存中共享變量的值, 從主內存中讀取最新的值

 

synchronized 實現可見性的緣由:

    線程釋放鎖前對共享變量的修改在下次持有鎖時對其餘線程可見

public class SynchronizedDemo {
	// 共享變量
	private int result = 0;

	// 共享變量執行自增操做
	public synchronized void increase() {
		result++;
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final SynchronizedDemo demo = new SynchronizedDemo();
		// 設置啓動500個線程
		int count = 500;
		// 建立一個 JUC 包下的同步同步計數器, 設置計數次數爲線程數500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 輸出: 500

synchronized 不只能夠實現可見性, 還能夠實現原子性

 

2. volatile 實現可見性

 

volatile 如何實現可見性:

    經過加入內存屏障和禁止指令重排序

    1) 對 volatile 變量執行寫操做時, 會在寫操做後加入一條 store 屏障指令

    2) 對 volatile 變量執行讀操做時, 會在讀操做前加入一條 load 屏障指令

 

public class VolatileDemo {
	// 使用 volatile 修飾共享變量
	private volatile int result = 0;

	// 共享變量 result 執行自增操做, 沒法保證原子性
	public void increase() {
		result++;
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final VolatileDemo demo = new VolatileDemo();
		// 設置啓動500個線程
		int count = 500;
		// 建立一個 JUC 包下的同步同步計數器, 設置計數次數爲線程數500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

    Console 輸出: 498

volatile 關鍵字, 能保證 volatile 變量的可見性, 不能保證 volatile 變量操做的原子性( 如 ++/-- )

 

3. AtomicInteger 實現可見性

    用 Atomic 類實現共享變量在線程中的原子性( 經過 CAS, 自旋 實現)

public class AtomicIntegerDemo {
	// 共享變量
	private AtomicInteger result = new AtomicInteger(0);

	// 使用 Atomic 類的 incrementAndGet() 方法( 原子操做 ), 實現自增
	public void increase() {
		result.incrementAndGet();
	}

	public int getResult() {
		return result.get();
	}

	public static void main(String[] args) throws InterruptedException {
		final AtomicIntegerDemo demo = new AtomicIntegerDemo();
		// 設置啓動500個線程
		int count = 500;
		// 建立一個JUC包下的同步同步計數器, 設置計數次數爲線程數500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 輸出: 500

 

4. JUC 包下的 Lock 實現可見性

    用 ReentrantLock 實現共享變量在線程中的原子性

public class LockDemo {
	// 共享變量
	private int result = 0;
	// 可重入鎖
	private Lock lock = new ReentrantLock();

	// 使用鎖機制, 保證鎖內代碼的原子性
	public void increase() {
		// 加鎖
		lock.lock();
		try {
			result++;
		} finally {
			// 釋放鎖
			lock.unlock();
		}
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final LockDemo demo = new LockDemo();
		// 設置啓動500個線程
		int count = 500;
		// 建立一個JUC包下的同步同步計數器, 設置計數次數爲線程數500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 輸出: 500

 

5. Semaphore 實現可見性

    用信號量機制實現共享變量在線程中的原子性

public class SemaphoreDemo {
	// 共享變量
	private int result = 0;
	// 初始化信號量爲1, 一次只能有1個線程訪問共享變量, 至關於互斥鎖
	private Semaphore semaphore = new Semaphore(1);

	// 使用信號量機制, 保證共享變量自增操做的原子性
	public void increase() {
		try {
			// 獲取1個信號量
			semaphore.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		result++;
		// 釋放1個信號量
		semaphore.release();
	}

	public int getResult() {
		return result;
	}

	public static void main(String[] args) throws InterruptedException {
		final SemaphoreDemo demo = new SemaphoreDemo();
		// 設置啓動500個線程
		int count = 500;
		// 建立一個JUC包下的同步同步計數器, 設置計數次數爲線程數500
		final CountDownLatch cdl = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					demo.increase();
					cdl.countDown();
				}
			}).start();
		}
		cdl.await();
		System.out.println(demo.getResult());
	}
}

Console 輸出: 500

 

總結:

synchronized 代碼塊具有 可見性 和 可見性

volatile 變量具有可見性, 不具有原子性

正確使用 volatile 變量

相關文章
相關標籤/搜索