目錄
題目:一個初始值爲零的變量,兩個線程對其交替操作,一個加1一個減1
題目:線程通信之順序調用 ReentrantLock,實現A->B->C三個線程順序執行
題目:線程通信之生產者消費者阻塞 BlockingQueue隊列版
阻塞隊列
首先它是一個隊列,而一個阻塞隊列在數據結構中所起的作用大致如下圖:
當阻塞隊列是空時,從隊列中獲取元素的操作將會被阻塞。
當阻塞隊列是滿時,往隊列中添加元素的操作將會被阻塞。
在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒。
爲什麼需要BlockingQueue
好處是:我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切BlockingQueue都給你一手包辦了。
在concurrent包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。
阻塞隊列的核心方法:
拋出異常 |
當阻塞隊列滿時,再往隊列裏add插入元素會拋出 java.lang.IllegalStateException: Queue full 當阻塞隊列空時,在往隊列裏remove移除元素會拋出 java.util.NoSuchElementException |
特殊值 |
插入方法,成功true 失敗false 移除方法,成功返回出隊列的元素,隊列裏沒有就返回null |
一直阻塞 |
當隊列滿時,生產者線程繼續往隊列裏put元素,隊列會一直阻塞生產線程直到put數據or響應中斷退出 當隊列空時,消費者試圖從隊列裏take元素,隊列會一直阻塞消費者線程直到隊列可用 |
超時退出 | 當阻塞隊列滿時,隊列會阻塞生產者一定時間,超過限時後生產者線程會退出 |
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
// 拋出異常
blockingQueue.add("a");// 添加
blockingQueue.remove();// 刪除
blockingQueue.remove("a");// 刪除
blockingQueue.element();// 檢查頭部元素
// 阻塞
blockingQueue.put("b");// 添加
blockingQueue.take();// 刪除
// 特殊值
blockingQueue.offer("c");// 添加
blockingQueue.poll();// 刪除
blockingQueue.offer("d", 3, TimeUnit.SECONDS);
blockingQueue.poll(3, TimeUnit.SECONDS);
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\tput 1");
blockingQueue.put("a");
System.out.println(Thread.currentThread().getName() + "\tput 2");
blockingQueue.put("b");
System.out.println(Thread.currentThread().getName() + "\tput 3");
blockingQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(() -> {
try {
try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
阻塞隊列的種類分析:
- ArrayBlockingQueue:由數組結構組成的有界阻塞隊列。
- LinkedBlockingQueue:由鏈表結構組成的有界阻塞隊列。(默認大小爲:Integer.MAX_VALUE)
- priorityBlockingQueue:支持優先級排序的無界阻塞隊列。
- DelayQueue:使用優先級隊列實現的延遲無界阻塞隊列。
- SynchronousQueue:不存儲元素的阻塞隊列,也即單個元素的隊列。
- LinkedTransferQueue:由鏈表結構組成的無界阻塞隊列。
- LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列。
初始值爲0的一個變量,兩個線程對其交替操作,一個加1一個減1,來5輪
1、高併發 線程操作資源類
2、判斷幹活、喚醒通知
3、嚴防多線程併發狀態下虛假喚醒
題目:一個初始值爲零的變量,兩個線程對其交替操作,一個加1一個減1
* 1、 線程 操作(方法) 資源類
* 2、 判斷 幹活 通知
* 3、 防止虛假喚醒機制
PS:多線程爲什麼要用while判斷? 2個線程的情況用 if 運行正確,擴展到4個線程以上用 if 判斷會出現虛假喚醒。
class ShareData{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incremenet()throws Exception{
lock.lock();
try {
// 1 判斷
while(number != 0){
// 等待,不能生產
condition.await();
}
// 2、幹活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3、 通知喚醒
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decremenet()throws Exception{
lock.lock();
try {
// 1 判斷
while(number == 0){
// 等待,不能生產
condition.await();
}
// 2、幹活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3、 通知喚醒
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
/**
* 題目:一個初始值爲零的變量,兩個線程對其交替操作,一個加1一個減1
* 1、 線程 操作(方法) 資源類
* 2、 判斷 幹活 通知
* 3、 防止虛假喚醒機制
*/
public class BlockingQueueDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
shareData.incremenet();
} catch (Exception e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
shareData.decremenet();
} catch (Exception e) {
e.printStackTrace();
}
}
},"BB").start();
}
}
題目:線程通信之順序調用 ReentrantLock,實現A->B->C三個線程順序執行
/**
* 多線程之間順序調用,實現A->B->C三個線程啓動,要求如下;
* AA打印5次,BB打印10次,CC打印15次
* 5循環10次
*/
class ShareResource{
private int number = 1; // A:1 B:2 C:3
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(){
lock.lock();
try {
// 1、判斷
while (number != 1){
c1.await();
}
// 2、幹活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3、通知
number = 2; // 修改標誌位
c2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
// 1、判斷
while (number != 2){
c2.await();
}
// 2、幹活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3、通知
number = 3; // 修改標誌位
c3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
// 1、判斷
while (number != 3){
c3.await();
}
// 2、幹活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3、通知
number = 1; // 修改標誌位
c1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print5();
}
},"AA").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print10();
}
},"BB").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print15();
}
},"CC").start();
}
}
題目:線程通信之生產者消費者阻塞 BlockingQueue隊列版
class MyResource{
private volatile boolean FLAG = true; // 默認開啓,進行生產+消費
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
// 構造注入方法
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd()throws Exception{
String data = null;
boolean retValue = false;
while(FLAG){
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if(retValue){
System.out.println(Thread.currentThread().getName() + "\t插入隊列" + data +"成功");
}else{
System.out.println(Thread.currentThread().getName() + "\t插入隊列" + data +"失敗");
}
try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + "\t大老闆叫停,表示flag=false,生產動作結束");
}
public void myConsumer()throws Exception{
while (FLAG){
String result = blockingQueue.poll(2, TimeUnit.SECONDS);
if(null == result || result.equalsIgnoreCase("")){
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t超過2秒鐘沒有取到蛋糕,消費退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t消費蛋糕" + result + "成功");
}
}
public void stop()throws Exception{
this.FLAG = false;
}
}
/**
* 線程通信之生產者消費者阻塞隊列版
*/
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t生產線程啓動");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t消費線程啓動");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "Consumer").start();
try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println();
System.out.println();
System.out.println("5秒鐘到,大老闆mian線程叫停,活動結束");
try {
myResource.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}