1. 程式人生 > >JDK併發包使用

JDK併發包使用

JDK併發包簡單使用

JUC

在 Java 5.0 提供了 java.util.concurrent(JUC)併發包,提供併發程式設計中很常用的工具類。

變數與執行緒安全

1、volatile
修飾成員變數
Java語言提供了一種稍弱的同步機制,即volatile變數,用來確保將變數的更新操作通知到其他執行緒;
保證此變數對所有的執行緒的可見性。

2、執行緒容器ThreadLocal
當前執行緒向容器中設定的值,只有當前執行緒獲取。其它執行緒無法獲取,避免了執行緒訪問資料的安全問題。
1)簡介
Java中的ThreadLocal類可以讓你建立的變數只被同一個執行緒進行讀和寫操作。因此,儘管有兩個執行緒同時執行一段相同的程式碼,而且這段程式碼又有一個指向同一個ThreadLocal變數的引用,但是這兩個執行緒依然不能看到彼此的ThreadLocal變數域。
2)程式碼示例
在這裡插入圖片描述

併發容器

(1)Hashtable 效率低
Map<String, String> hashtable = new Hashtable<>();

(2)synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());

(3)ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
示例程式碼:

public class DefaultTokenManager implements TokenManager {
    private static Map<String, String> tokenMap = new ConcurrentHashMap<>();
    @Override
    public String createToken(String username) {
        String token = CodecUtil.createUUID();
        tokenMap.put(token, username);
        return token;
    }
    @Override
    public boolean checkToken(String token) {
        return !StringUtil.isEmpty(token) && tokenMap.containsKey(token);
    }
}

併發佇列

安全佇列
佇列容器,存放資料,當佇列中資料被取完時,取資料操作執行緒被堵塞;當佇列中資料被加滿是,加資料操作執行緒被堵塞

BlockingQueue介面多個實現類,一下為其中三個實現類

(1)ArrayBlockingQueue是一個有邊界的阻塞佇列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。

BlockingQueue queue = new ArrayBlockingQueue(1024);
queue.put("1");
Object object = queue.take();

(2)LinkedBlockingQueue阻塞佇列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是採用了預設大小為Integer.MAX_VALUE的容量 。它的內部實現是一個連結串列。和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式儲存資料。

BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();
BlockingQueue<String> bounded   = new LinkedBlockingQueue<String>(1024);
bounded.put("Value");
String value = bounded.take();

(3)SynchronousQueue佇列內部僅允許容納一個元素。當一個執行緒插入一個元素後會被阻塞,除非這個元素被另一個執行緒消費。

併發工具類

CountDownLatch、Semaphore、ReentrantReadWriteLock、ReentrantLock

(1)CountDownLatch
用於監聽某些初始化操作,等初始化操作完畢後,通知主執行緒繼續執行

final CountDownLatch countDown = new CountDownLatch(2);

countDown.await();  //執行緒阻塞   
countDown.countDown();  //啟用一次
countDown.countDown();  //啟用二次

(2)Semaphore
Semaphore是計數訊號量,經常用於限制獲取某種資源的執行緒數量

final Semaphore semp = new Semaphore(3);     
semp.acquire();   //獲取許可
//業務程式碼              //這裡面只允許3個執行緒來執行
semp.release();   //釋放許可

(3)讀寫鎖執行緒安全ReentrantReadWriteLock

private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReadLock readLock = rwLock.readLock();    //獲取讀鎖
private final WriteLock wirteLock = rwLock.writeLock();    //獲取寫鎖

readLock.lock();
readLock.unlock();

讀讀共享,寫寫互斥,讀寫互斥

讀寫鎖:分為讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,這是由jvm自己控制的,你只要上好相應的鎖即可。

  • 如果你的程式碼只讀資料,可以很多人同時讀,但不能同時寫,那就上讀鎖;
  • 如果你的程式碼修改資料,只能有一個人在寫,且不能同時讀取,那就上寫鎖。
  • 總之,讀的時候上讀鎖,寫的時候上寫鎖!
    private Object data = null; //共享資料 ,只能有一個執行緒寫該資料,但可以有多個執行緒同時讀
    ReadWriteLock rwl = new ReentrantReadWriteLock(); //讀寫鎖
    rwl.readLock().lock();//上讀鎖 可以有多個執行緒同時讀 不管是否異常做釋放鎖操作
    rw1.writeLock().lock();//新增寫鎖,保證只能有一個執行緒進行寫操作 不管是否異常做釋放鎖操作

(4)執行緒安全ReentrantLock

定義鎖:private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
使用:
lock.lock();加鎖  
c1.await();   //執行緒阻塞 等待,c1.signal(); //發訊號解除阻塞
lock.unlock();釋放鎖

執行緒池

實現執行緒池建立執行緒比手動new Thread()好,效率高,統一管理

(1)Java通過Executors提供四種執行緒池:

①newCachedThreadPool建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
②newFixedThreadPool 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。Runtime.getRuntime().availableProcessors()
③newScheduledThreadPool 建立一個定長執行緒池,支援定時及週期性任務執行。
④newSingleThreadExecutor 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

(2)簡單示例

cachedThreadPool.execute(new Runnable(){...})    //需要傳入實現Runnable介面的的業務類
scheduledThreadPool.schedule(new Runnable(){...},3, TimeUnit.SECONDS)   //延遲3秒執行
scheduledThreadPool.schedule(new Runnable(){...},1,3, TimeUnit.SECONDS)   //延遲1秒後,每隔3秒執行一次   定時週期執行任務   比Timeer

FutureTask future = new FutureTask(new UserFuture());   //構造FutureTask,傳入業務程式碼執行的物件,該類需要實現Callable介面

ExecutorService executor = Executors.newFixedThreadPool(1);  //定義執行緒池,裡面可開闢兩個執行緒
Future f = executor.submit(future);  //單獨啟動一個執行緒(執行緒池)去執行  不影響下面程式碼執行
f.get();   //返回null表示任務執行完成
future.get();  //非同步獲取業務類處理的結果  並阻塞一個執行緒