併發包(java.util.concurrent)
爲了更好的支持併發程序,jdk內部提供了大量實用的API和框架
同步控制(鎖)
鎖(lock)可以完全代替關鍵字synchronize.
jdk中鎖是一個接口,提供了三個實現類,讀鎖,寫鎖,重入鎖.
讀寫鎖
讀寫鎖拆成讀鎖和寫鎖來理解。讀鎖可以共享,多個線程可以同時擁有讀鎖,但是寫鎖卻只能只有一個線程擁有,而且獲取寫鎖的時候其他線程都已經釋放了讀鎖,而且該線程獲取寫鎖之後,其他線程不能再獲取讀鎖。簡單的說就是寫鎖是排他鎖,讀鎖是共享鎖。
下面代碼開啓了10個讀取線程,10個寫線程
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
try {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " 開始讀取");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 讀取完畢");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void write() {
try {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " 開始寫數據");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 寫數據完畢");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockTest rwlt = new ReadWriteLockTest();
for(int i = 0 ;i < 10 ; i ++){
new Thread() {
@Override
public void run() {
rwlt.read();
}
}.start();
new Thread() {
@Override
public void run() {
rwlt.write();
}
}.start();
}
}
}
打印結果
Thread-0 開始讀取
Thread-0 讀取完畢
Thread-1 開始寫數據
Thread-1 寫數據完畢
Thread-3 開始寫數據
Thread-3 寫數據完畢
Thread-2 開始讀取
Thread-4 開始讀取
Thread-2 讀取完畢
Thread-4 讀取完畢
Thread-5 開始寫數據
Thread-5 寫數據完畢
Thread-6 開始讀取
Thread-6 讀取完畢
Thread-7 開始寫數據
Thread-7 寫數據完畢
Thread-8 開始讀取
Thread-8 讀取完畢
Thread-9 開始寫數據
Thread-9 寫數據完畢
Thread-10 開始讀取
Thread-10 讀取完畢
Thread-11 開始寫數據
Thread-11 寫數據完畢
Thread-12 開始讀取
Thread-12 讀取完畢
Thread-13 開始寫數據
Thread-13 寫數據完畢
Thread-14 開始讀取
Thread-14 讀取完畢
Thread-15 開始寫數據
Thread-15 寫數據完畢
Thread-16 開始讀取
Thread-16 讀取完畢
Thread-17 開始寫數據
Thread-17 寫數據完畢
Thread-18 開始讀取
Thread-18 讀取完畢
Thread-19 開始寫數據
Thread-19 寫數據完畢
重入鎖
可重入鎖的概念是自己可以再次獲取自己的內部鎖。舉個例子,比如一條線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其再次想要獲取這個對象的鎖的時候還是可以獲取的(如果不可重入的鎖的話,此刻會造成死鎖)。說的更高深一點可重入鎖是一種遞歸無阻塞的同步機制。
//打印類
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Print {
private Lock lock = new ReentrantLock();
public void prt(){
lock.lock();
for(int i = 0 ; i < 5 ; i++){
System.out.println(Thread.currentThread().getName() + " : " + i);
}
lock.unlock();
}
}
//線程類
public class Th1 implements Runnable{
private Print p;
public Th1(Print p) {
this.p = p;
}
@Override
public void run() {
p.prt();
}
}
//Main方法類
public class M {
public static void main(String[] args) {
Print p = new Print();
new Thread(new Th1(p)).start();
new Thread(new Th1(p)).start();
}
}
在重入鎖中提供了公平鎖和非公平鎖 構造的時候傳入參數 true/false 來區分
公平鎖與非公平鎖
公平表示線程獲取鎖的順序是按照線程加鎖的順序來分配的,即先來先得的FIFO順序。而非公平就是一種獲取鎖的搶佔機制,和公平相對就是先來不一定先得,這個方式可能造成某些線程飢餓(一直拿不到鎖)
Condition(併發包中的wait與notify)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition 配合Lock 實現線程的等待 與通知
*/
public class ConditionTest{
public static ReentrantLock lock=new ReentrantLock();
public static Condition condition =lock.newCondition();
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
lock.lock();//請求鎖
try{
System.out.println(Thread.currentThread().getName()+"==》進入等待");
condition.await();//設置當前線程進入等待
}catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//釋放鎖
}
System.out.println(Thread.currentThread().getName()+"==》繼續執行");
}
}.start();
new Thread(){
@Override
public void run() {
lock.lock();//請求鎖
try{
System.out.println(Thread.currentThread().getName()+"=》進入");
Thread.sleep(2000);//休息2秒
condition.signal();//隨機喚醒等待隊列中的一個線程
System.out.println(Thread.currentThread().getName()+"休息結束");
}catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//釋放鎖
}
}
}.start();
}
}
Semaphore(信號量)
信號量爲多線程提供了更爲強大的控制方法,無論是鎖還是synchronize,一次都只允許一個線程訪問一個資源,而信號量可以指定多少個線程,同時訪問某一個資源.
通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可,
//打印類
import java.util.concurrent.Semaphore;
public class Print {
private Semaphore semaphore = new Semaphore(5);
public void prt(){
try {
System.out.println(Thread.currentThread().getName() + " : 準備進入" );
semaphore.acquire();
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " : 進入" );
Thread.sleep(3000);
semaphore.release();
System.out.println(Thread.currentThread().getName() + " : 離開" );
} catch (Exception e) {
e.printStackTrace();
}
}
}
//線程類
public class Th1 implements Runnable{
private Print p;
public Th1(Print p) {
this.p = p;
}
@Override
public void run() {
p.prt();
}
}
//Main方法類
public class M {
public static void main(String[] args) {
Print p = new Print();
for(int i = 0 ; i < 14 ; i++){
new Thread(new Th1(p)).start();
}
}
}
計數器(CountDownLatch)
主線程模擬裁判,八個子線程模擬運動員,裁判和八個運動員都就位以後,運動員才能開始跑步
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownTest {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
final CountDownLatch cdl1 = new CountDownLatch(1);//裁判吹哨開始倒計時,歸零運動員開始跑步
final CountDownLatch cdl2 = new CountDownLatch(8);//運動員跑完了,裁判公佈結果。//cdl2.countDown() 這個方法 每次到達一個會減少1 而不是一次減完 這裏八個運動員跑步 所以這裏 實例化對象的 參數 應該是 8
final Map<String , Long> map = new HashMap<String , Long>();
for(int i =0 ; i < 8 ;i ++){//八個運動員
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("運動員 " + Thread.currentThread().getName() + " 就位");
try {
cdl1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("運動員:" + Thread.currentThread().getName() + "開始跑步");
try {
Thread.sleep(new Random().nextInt(10) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(Thread.currentThread().getName(), new Date().getTime());
System.out.println("運動員:" + Thread.currentThread().getName() + " 跑完");
cdl2.countDown();
}
});
}
long l = 0;
try {
System.out.println("裁判就位");
Thread.sleep(3000);
cdl1.countDown();
System.out.println("裁判發了起跑消息,運動員起跑 , 裁判等待跑完");
l = new Date().getTime();
cdl2.await();
System.out.println("跑完了,裁判發佈結果");
System.out.println("結果如下");
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.shutdown();
for(Entry<String, Long> entry : map.entrySet()){
System.out.println(entry.getKey() + " 所用所用時間是 :"
+ (entry.getValue() - l));
}
}
}
運動員 pool-1-thread-1 就位
運動員 pool-1-thread-4 就位
運動員 pool-1-thread-3 就位
運動員 pool-1-thread-2 就位
裁判就位
運動員 pool-1-thread-6 就位
運動員 pool-1-thread-7 就位
運動員 pool-1-thread-5 就位
運動員 pool-1-thread-8 就位
裁判發了起跑消息,運動員起跑 , 裁判等待跑完
運動員:pool-1-thread-1開始跑步
運動員:pool-1-thread-3開始跑步
運動員:pool-1-thread-2開始跑步
運動員:pool-1-thread-4開始跑步
運動員:pool-1-thread-8開始跑步
運動員:pool-1-thread-5開始跑步
運動員:pool-1-thread-7開始跑步
運動員:pool-1-thread-6開始跑步
運動員:pool-1-thread-3 跑完
運動員:pool-1-thread-5 跑完
運動員:pool-1-thread-7 跑完
運動員:pool-1-thread-6 跑完
運動員:pool-1-thread-2 跑完
運動員:pool-1-thread-8 跑完
運動員:pool-1-thread-1 跑完
運動員:pool-1-thread-4 跑完
跑完了,裁判發佈結果
結果如下
pool-1-thread-4 所用所用時間是 :7999
pool-1-thread-5 所用所用時間是 :2999
pool-1-thread-2 所用所用時間是 :5998
pool-1-thread-3 所用所用時間是 :998
pool-1-thread-1 所用所用時間是 :7998
pool-1-thread-7 所用所用時間是 :2999
pool-1-thread-6 所用所用時間是 :3999
pool-1-thread-8 所用所用時間是 :5999
CyclicBarrier(循環柵欄)
循環柵欄與計算器很像,但是可以反覆使用,下面模擬 十個人一起去景點的場景.
//線程類
package c;
import java.util.Random;
import java.util.concurrent.CyclicBarrier;
public class Th1 implements Runnable{
private CyclicBarrier cb;
public Th1(CyclicBarrier cb2) {
this.cb = cb2;
}
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println(Thread.currentThread().getName() + "到了黃鶴樓 已經有:" + (cb.getNumberWaiting() + 1) + "個到達黃鶴樓");
if(cb.getNumberWaiting() == 10){
System.out.println("全部到齊,下一站龜山");
}
cb.await();
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println(Thread.currentThread().getName() + "到了龜山 已經有:" + (cb.getNumberWaiting() + 1) + "個到達龜山");
if(cb.getNumberWaiting() == 10){
System.out.println("全部到齊,下一站東湖");
}
cb.await();
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println(Thread.currentThread().getName() + "到了東湖 已經有:" + (cb.getNumberWaiting() + 1) + "個到達東湖");
if(cb.getNumberWaiting() == 10){
System.out.println("全部到齊,結束");
}
cb.await();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
//Main方法類
package c;
import java.util.concurrent.CyclicBarrier;
public class M {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(10);
for(int i = 0 ; i < 10 ; i++){
new Thread(new Th1(cb)).start();
}
}
}
打印結果
Thread-3到了黃鶴樓 已經有:1個到達黃鶴樓
Thread-7到了黃鶴樓 已經有:2個到達黃鶴樓
Thread-8到了黃鶴樓 已經有:3個到達黃鶴樓
Thread-9到了黃鶴樓 已經有:4個到達黃鶴樓
Thread-1到了黃鶴樓 已經有:5個到達黃鶴樓
Thread-5到了黃鶴樓 已經有:6個到達黃鶴樓
Thread-2到了黃鶴樓 已經有:7個到達黃鶴樓
Thread-0到了黃鶴樓 已經有:7個到達黃鶴樓
Thread-6到了黃鶴樓 已經有:9個到達黃鶴樓
Thread-4到了黃鶴樓 已經有:10個到達黃鶴樓
Thread-6到了龜山 已經有:1個到達龜山
Thread-1到了龜山 已經有:1個到達龜山
Thread-5到了龜山 已經有:3個到達龜山
Thread-3到了龜山 已經有:4個到達龜山
Thread-8到了龜山 已經有:4個到達龜山
Thread-2到了龜山 已經有:6個到達龜山
Thread-4到了龜山 已經有:7個到達龜山
Thread-9到了龜山 已經有:8個到達龜山
Thread-7到了龜山 已經有:9個到達龜山
Thread-0到了龜山 已經有:10個到達龜山
Thread-3到了東湖 已經有:1個到達東湖
Thread-0到了東湖 已經有:2個到達東湖
Thread-8到了東湖 已經有:3個到達東湖
Thread-4到了東湖 已經有:4個到達東湖
Thread-2到了東湖 已經有:4個到達東湖
Thread-5到了東湖 已經有:4個到達東湖
Thread-6到了東湖 已經有:7個到達東湖
Thread-1到了東湖 已經有:7個到達東湖
Thread-9到了東湖 已經有:9個到達東湖
Thread-7到了東湖 已經有:10個到達東湖