Java 併發包應用

一、Java JUC

​ java5.0 提供的 java.util.concurrent 包,其中增加了併發編程中常見的工具類。

​ eg: 線程池、異步IO 和 輕量級任務框架

​ 提供可調的、靈活的線程池,以及提供了設計用於多線程上下文中的 Collection 實現。

二、volatile ——解決內存可見性

內存可見性(Memory Visibility)指當某個線程 A 正在使用對象狀態而另一個線程 B 在同時修改該狀態,需要確保當一個線程 B 修改了對象狀態後,其他線程(例如 A)能夠看到發生的狀態變化。

解決方法:
① 通過同步來保證對象被安全地發佈(synchronized 關鍵字);
② 使用更加輕量的 volatile 變量

volatile 和 synchronized 區別:

volatile 使用了線程中內存柵欄實時從主存中進行數據刷新;而 synchronized 同步變量時,由於JVM 底層的重排序,所以性能比較低

注意:1. volatile 不具備“互斥性”;2. volatile 不能保證變量的“原子性”

public class TestVolatile {
	
	public static void main(String[] args) {
		ThreadDemo threadDemo = new ThreadDemo();
		// 啓動線程
		new Thread(threadDemo).start();
		
		while(true) {
// 方式二: 在要操作的共享數據前加 volatile 關鍵字
			// 保證數據可見性
			if (threadDemo.isFlag()) {
				System.out.println("volatile result --->");
				break;
			}			
			
// 方式一: 直接加同步鎖, 性能低			
			// 內存可見性問題: 當多個線程操作共享數據時, 彼此不可見
			// 對新建的線程進行加同步鎖, 使得每次都從主存中獲取數據
			// 耗費性能
/*			synchronized (threadDemo) {
				if (threadDemo.isFlag()) {
					System.out.println("--->");
					break;					
				}
			}
*/
		}
	}
}

class ThreadDemo implements Runnable {
	private volatile boolean flag = false;
	
	@Override
	public void run() {
		flag = true;
		System.out.println("falg = " + flag);
	}
	
	public boolean isFlag() {
		return flag;
	}	
}

三、原子變量 CAS 算法

3.1 CAS 算法

CAS(Compare-And-Swap)是一種硬件對併發的支持,針對多處理器操作而設計的處理器中的一種特殊指令,用於管理對共享數據的併發訪問。

CAS 是一種無鎖的非阻塞算法的實現。

CAS 包含了三個操作數:

  • 內存值 V; 預估值 A; 更新值 B
  • 當且僅當 V == A 時, V = B. 否則將不做任何操作

CAS 算法效率比鎖的效率高, 因爲更新失敗的時候不會放棄 CPU 時間片

"讀-改-寫"

3.2 原子變量

類的小工具包,支持在單個變量上解除鎖的線程安全編程。事實上,此包中的類可將volatile 值、字段和數組元素的概念擴展到那些也提供原子條件更新操作的類。

AtomicBoolean、AtomicInteger、AtomicLong 和AtomicReference 的實例各自提供對相應類型單個變量的訪問和更新。每個類也爲該類型提供適當的實用工具方法。

AtomicIntegerArray、AtomicLongArray 和AtomicReferenceArray 類進一步擴展了原子操作,對這些類型的數組提供了支持。這些類在爲其數組元素提供 volatile 訪問語義方面也引人注目,這對於普通數組來說是不受支持的。

核心方法:boolean compareAndSet(expectedValue, updateValue)

java.util.concurrent.atomic 包下提供了一些原子操作的常用類

public class TestAtomicDemo {
	public static void main(String[] args) {
		AtomicDemo atomicDemo = new AtomicDemo();
		
		for (int i = 0; i < 20; i++) {
			Thread thread = new Thread(atomicDemo);
			thread.start();
		}
	}	
}

class AtomicDemo implements Runnable {
//	private int serialNumber = 0;
	private AtomicInteger serialNumber = new AtomicInteger(0);
	
	@Override
	public void run() {
		try {
			// 線程休眠 200 毫秒
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ": " + getSerialNumber());
	}
	
	public int getSerialNumber() {
//		return serialNumber++;
		return serialNumber.getAndIncrement();
	}
}

四、ConcurrentHashMap

採用 “鎖分段” 機制

– concurrentLevel 鎖級別: 默認16個段;

每個段都是一個鎖, 每個段有一個 hash 表
並行

CopyOnWriteArrayList/CopyOnWriteArraySet: “寫入並複製”

注意:
​ · 添加操作多不適合使用, 效率低, 每次添加操作時會先做複製
​ · 併發迭代操作多時, 可以使用

-> 用法與 HashMap 相同

五、CountDownLatch 閉鎖操作

CountDownLatch 是一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。

閉鎖可以延遲線程的進度直到其到達終止狀態,閉鎖可以用來確保某些活動直到其他活動都完成才繼續執行:

  • 確保某個計算在其需要的所有資源都被初始化之後才繼續執行;
  • 確保某個服務在其依賴的所有其他服務都已經啓動之後才啓動;
  • 等待直到某個操作所有參與者都準備就緒再繼續執行。
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch: 閉鎖, 在完成某些運算時, 只有當其他所有線程的運算全部完成, 當前計算才繼續運行
 * 
 * 
 * @author Jashon
 * @time 2019-02-07 19:22:03
 * @version 1.0
 *
 */
public class TestCountDownLatch {

	public static void main(String[] args) {
		final CountDownLatch latch = new CountDownLatch(5);
		
		Instant start = Instant.now();
		
		LatchDemo latchDemo = new LatchDemo(latch);
		
		for (int i = 0; i < 5; i++) {
			new Thread(latchDemo).start();
		}
		try {
			// 主線程進入等待, 直到 latch 爲 0, 此方法立即返回 
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		Instant end = Instant.now();
		Duration duration = Duration.between(start, end);
		long seconds = duration.getSeconds();
		System.out.println("耗費時間: " + seconds + " s");
	}
}

class LatchDemo implements Runnable {

	private CountDownLatch latch;
	
	public LatchDemo(CountDownLatch latch) {
		this.latch = latch;
	}

	@Override
	public void run() {
		synchronized (this) {
			try {
				for(int i = 0; i < 50000; i++) {
					if(i % 2 == 0) {
						System.out.println(i);
					}
				}
			} finally {
				// 將維護的變量減 1
				latch.countDown();
			}
		}
	}
}

六、Callable 接口

Callable 需要依賴 FutureTask ,FutureTask 也可以用作 閉鎖操作

Callable 接口類似於Runnable,但是Runnable 不會返回結果,並且無法拋出經過檢查的異常。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 一、創建執行線程的方式三:實現  Callable 接口
 * 		相較於 Runable 接口的方式, 方法可以有返回值, 並且可以拋出異常
 * 
 * 二、執行Callable 方式, 需要 FutureTask 實現類的支持, 用於接收運算結果。
 * 		FutureTask 是 Future 的實現類
 * 
 * @author Jashon
 * @time 2019-02-07 19:50:30
 * @version 1.0
 *
 */
public class TestCallable {

	public static void main(String[] args) {
		ThreadDemo1 threadDemo = new ThreadDemo1();
		
		FutureTask<Integer> result = new FutureTask<>(threadDemo);
		
		new Thread(result).start();
		
		try {
			// 接受線程運算後的結果
			// 相當於一個 閉鎖 操作, 可用於 閉鎖
			Integer sum = result.get();
			System.out.println(sum);
			System.out.println("------------------------");
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}		
	}
}

class ThreadDemo1 implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		
		for (int i = 0; i <= 100; i++) {
			sum += i;
			System.out.println(i);
		}
		return sum;
	}
}

七、同步鎖 Lock

Java 5.0 之前,協調共享對象的訪問時可以使用的機制只有 synchronized 和 volatile 。Java 5.0 後增加了一些新的機制,但並不是一種替代內置鎖的方法,而是當內置鎖不適用時,作爲一種可選擇的高級功能。

**ReentrantLock 實現了Lock 接口,並提供了與 synchronized 相同的 互斥性 和 內存可見性。**但相較於 synchronized 提供了更高的處理鎖的靈活性。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 一、用於解決多線程安全問題的方式:
 * 		synchronized: 隱式鎖
 * 		1. 同步代碼塊
 * 		2. 同步方法
 * 
 * jdk 1.5 後:
 * 	3. 同步鎖 lock
 * 
 * 注意: 是一個顯示鎖, 需要通過 lock() 方法上鎖, 
 * 		必須通過 unlock() 方法進行釋放鎖(一般放於finally{ .unlock(); })
 * 
 * @author Jashon
 * @time 2019-02-07 20:06:03
 * @version 1.0
 */
public class TestLock {

	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		new Thread(ticket, "售票口1").start();
		new Thread(ticket, "售票口2").start();
		new Thread(ticket, "售票口3").start();
	}
}


class Ticket implements Runnable {
    
	private int tick = 100;
	
	// 創建顯示鎖
	public Lock lock = new ReentrantLock();
	
	@Override
	public void run() {
		while(true) {
			lock.lock();
			
			try {
				if(tick > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
					System.out.println(Thread.currentThread().getName() + "完成售票, 餘票爲:" + --tick);
				}
			}finally {
				lock.unlock();
			}
		}
	}	
}

八、Condition 控制線程通信(與 Lock 一同使用)

Condition 接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能。

特別的是,單個Lock 可能與多個Condition 對象關聯。爲了避免兼容性問題,Condition 方法的名稱與對應的Object 版本中的不同。

Lock 同步鎖中,Condition 對象中,與wait、notify、notifyAll 方法對應的分別是await、signal、signalAll

Condition 實例實質上被綁定到一個鎖上。要爲特定Lock 實例獲得Condition 實例,使用其 newCondition() 方法。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用 Lock 同步鎖實現交替切換線程
 * 
 * @author Jashon
 * @time 2019-02-08 20:22:35
 * @version 1.0
 *
 */
public class TestABCAlternate {

	public static void main(String[] args) {
		AlternateDemo alternateDemo = new AlternateDemo();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 0; i <=20; i++) {
					alternateDemo.loopA(i);
				}
			}
		}, "A").start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 0; i <=20; i++) {
					alternateDemo.loopB(i);
				}
			}
		}, "B").start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 0; i <=20; i++) {
					alternateDemo.loopC(i);
				}
			}
		}, "C").start();
	}
}

class AlternateDemo {
	private int number = 1;		// 當前線程執行的標記
	
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	private Condition condition1 = lock.newCondition();
	private Condition condition2 = lock.newCondition();
	
	public void loopA(int totalLoop) {
		lock.lock();
		try {
			// 1. 判斷
			if(number != 1) {
				condition.await();
			}
			
			// 2. 打印
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName() + "\t " + i + "\t" + totalLoop);
			}
			
			// 3. 喚醒
			number = 2;
			condition1.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void loopB(int totalLoop) {
		lock.lock();
		try {
			// 1. 判斷
			if(number != 2) {
				condition1.await();
			}
			
			// 2. 打印
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName() + "\t " + i + "\t" + totalLoop);
			}
			
			// 3. 喚醒
			number = 3;
			condition2.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void loopC(int totalLoop) {
		lock.lock();
		try {
			// 1. 判斷
			if(number != 3) {
				condition2.await();
			}
			
			// 2. 打印
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName() + "\t " + i + "\t" + totalLoop);
			}
			
			// 3. 喚醒
			number = 1;
			condition.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

九、ReadWriteLock 讀寫鎖

ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。只要沒有writer,讀取鎖可以由多個 reader 線程同時保持。寫入鎖是獨佔的

ReadWriteLock 讀取操作通常不會改變共享資源,但執行寫入操作時,必須獨佔方式來獲取鎖。對於讀取操作佔多數的數據結構。ReadWriteLock 能提供比獨佔鎖更高的併發性。而對於只讀的數據結構,其中包含的不變性可以完全不需要考慮加鎖操作。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * ReadWriteLock: 讀寫鎖
 * 
 * 		寫寫/讀寫: 互斥
 * 		
 * 		讀讀: 不需要互斥
 * 
 * @author Jashon
 * @time 2019-02-08 22:31:44
 * @version 1.0
 *
 */
public class TestReadAndWrite {
	
	public static void main(String[] args) {
		ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				readWriteLockDemo.set(10);
			}
		}, "WRITE:").start();
		
		for (int i = 0; i < 20; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					readWriteLockDemo.get();
				}
			}).start();
		}
	}
}

class ReadWriteLockDemo {
	private int number = 0;
	
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	// 讀
	public void get() {
		lock.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " : " + number);
		} finally {
			lock.readLock().unlock();
		}
	}
	
	// 寫
	public void set(int number) {
		lock.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName());
			this.number = number;
		} finally {
			lock.writeLock().unlock();
		}
	}
}

十、線程池

  1. **線程池:**提供了一個線程隊列,隊列中保存着所有等待狀態的線程。避免了創建與銷燬額外開銷,提高了響應的速度。

  2. 線程池的體系結構:
    java.util.concurrent.Executor : 負責線程的使用與調度的根接口
    ​ |–**ExecutorService 子接口: 線程池的主要接口
    ​ |–ThreadPoolExecutor 線程池的實現類
    ​ |–ScheduledExecutorService 子接口:負責線程的調度
    ​ |–ScheduledThreadPoolExecutor :繼承 ThreadPoolExecutor, 實現 ScheduledExecutorService

  3. 工具類 : Executors
    ExecutorService newFixedThreadPool() : 創建固定大小的線程池
    ExecutorService newCachedThreadPool() : 緩存線程池,線程池的數量不固定,可以根據需求自動的更改數量。
    ExecutorService newSingleThreadExecutor() : 創建單個線程池。線程池中只有一個線程

    ScheduledExecutorService newScheduledThreadPool() : 創建固定大小的線程,可以延遲或定時的執行任務。

public class TestThreadPool {
	
	public static void main(String[] args) throws Exception {
		//1. 創建線程池
		ExecutorService pool = Executors.newFixedThreadPool(5);
		
		List<Future<Integer>> list = new ArrayList<>();
		
		for (int i = 0; i < 10; i++) {
			Future<Integer> future = pool.submit(new Callable<Integer>(){
				@Override
				public Integer call() throws Exception {
					int sum = 0;
					
					for (int i = 0; i <= 100; i++) {
						sum += i;
					}
					return sum;
				}
			});
			list.add(future);
		}
		
		pool.shutdown();
		
		for (Future<Integer> future : list) {
			System.out.println(future.get());
		}
		
		/*ThreadPoolDemo tpd = new ThreadPoolDemo();
		
		//2. 爲線程池中的線程分配任務
		for (int i = 0; i < 10; i++) {
			pool.submit(tpd);
		}
		
		//3. 關閉線程池
		pool.shutdown();*/
	}
	
//	new Thread(tpd).start();
//	new Thread(tpd).start();
}

class ThreadPoolDemo implements Runnable{

	private int i = 0;
	
	@Override
	public void run() {
		while(i <= 100){
			System.out.println(Thread.currentThread().getName() + " : " + i++);
		}
	}	
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章