JUC多線程及高并發(fā)
請(qǐng)談?wù)勀銓?duì)volatile的理解
????volatile是Java虛擬機(jī)提供的輕量級(jí)的同步機(jī)制
? ? 特點(diǎn)是 保證可見(jiàn)性 不保證原子性 禁止指令重排
什么是可見(jiàn)性?
????一個(gè)線程AAA修改了共享變量X的值但還未寫回主內(nèi)存時(shí)蚪燕,另外一個(gè)線程BBB又對(duì)主內(nèi)存中同一個(gè)共享變量X進(jìn)行操作瘫怜,但此時(shí)AAA線程工作內(nèi)存中共享變量X對(duì)線程BBB來(lái)說(shuō)并不可見(jiàn),這種工作內(nèi)存與主內(nèi)存同步延遲現(xiàn)象就造成了可見(jiàn)性問(wèn)題
可見(jiàn)性問(wèn)題演示:
package com.example.demo;
import java.util.concurrent.TimeUnit;
class MyData {
? ? //請(qǐng)注意,此時(shí)number沒(méi)加volatile關(guān)鍵字修飾的向抢,不保證可見(jiàn)性
? ? int number = 0;
? ? public void addTO60() {
? ? ? ? this.number = 60;
? ? }
}
/**
* 可見(jiàn)性問(wèn)題演示
*/
public class VolatileDemo {
? ? public static void main(String[] args) {
? ? ? ? MyData myData = new MyData();//資源類
? ? ? ? new Thread(() -> {
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t come in");
? ? ? ? ? ? //暫停一會(huì)兒線程
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(3);
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? myData.addTO60();
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);
? ? ? ? }, "AAA").start();
? ? ? ? //第2個(gè)線程就是我們的main線程
? ? ? ? while (myData.number == 0) {
? ? ? ? ? ? //main線程就一直再這里等待循環(huán)套耕,直到number值不再等于0
? ? ? ? }
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t mission is over,main get number value: " + myData.number);
? ? }
}
可見(jiàn)性問(wèn)題解決:
volatile int number =0;
什么是原子性?
不可分割惶凝,也即某個(gè)線程正在做某個(gè)具體業(yè)務(wù)時(shí)吼虎,中間不可以被加塞或者被分割。需要整體完整苍鲜。
要么同時(shí)成功思灰,要么同時(shí)失敗
原子性問(wèn)題演示:
package com.example.demo;
class MyData {
? ? //請(qǐng)注意,此時(shí)number前面是加了volatile關(guān)鍵字修飾的混滔,volatile不保證原子性
? ? volatile int number = 0;
? ? public void addPlusPlus(){
? ? ? ? number++;
? ? }
}
/**
* 原子性問(wèn)題演示
*/
public class VolatileDemo {
? ? public static void main(String[] args) {
? ? ? ? MyData myData = new MyData();//資源類
? ? ? ? for (int i = 1; i <= 20; i++) {
? ? ? ? ? ? new Thread(() ->{
? ? ? ? ? ? ? ? for (int j = 1; j <= 1000; j++) {
? ? ? ? ? ? ? ? ? ? myData.addPlusPlus();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? ? ? //需要等待上面20個(gè)線程都全部計(jì)算完成后洒疚,再用main線程取得最終的結(jié)果值看是多少
? ? ? ? while (Thread.activeCount() > 2){
? ? ? ? ? ? Thread.yield();
? ? ? ? }
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t int type, finally number value: "+myData.number);
? ? }
}
原子性問(wèn)題解決:
加sync
加lock
使用JUC下Atomic
有序性(禁止指令)
計(jì)算機(jī)在執(zhí)行程序時(shí),為了提高性能坯屿,編譯器和處理器常常會(huì)對(duì)指令做重排油湖,一般分以下3種
源代碼—>編譯器優(yōu)化的重拍—>指令并行的重拍—>內(nèi)存系統(tǒng)的重排—>最終執(zhí)行的指令
處理器在進(jìn)行重排序時(shí)必須要考慮指令之間的數(shù)據(jù)依賴性
多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在领跛,兩個(gè)線程中使用的變量能否保證一致性時(shí)無(wú)法確定的乏德,結(jié)果無(wú)法預(yù)測(cè)、
案例:
public void mySort(){
????int x =11;//語(yǔ)句1
? ? int y =q12;//語(yǔ)句2
? ? x = x +5;//語(yǔ)句3
? ? y = x * x;//語(yǔ)句4
}
執(zhí)行順序可能為以下任何一種:1234 ?? 2134 ?? 1324
請(qǐng)問(wèn)語(yǔ)句4可以重排后變成第一條嗎吠昭?
答:不可以喊括,因?yàn)樘幚砥髟谶M(jìn)行重排序時(shí)必須要考慮指令之間的數(shù)據(jù)依賴性
禁止指令重排小總結(jié)
volatile實(shí)現(xiàn)禁止指令重排優(yōu)化,從而避免多線程環(huán)境下程序出現(xiàn)亂序執(zhí)行的現(xiàn)象
原理是通過(guò)插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化
你在哪些地方用到過(guò)volatile矢棚?
多線程下的單例模式DCL(雙端檢鎖):
package com.example.demo;
public class SingletonDemo {
? ? private static volatile SingletonDemo instance = null;
? ? private SingletonDemo(){
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 我時(shí)構(gòu)造方法SingletonDemo()");
? ? }
? ? //DCL(Doouble Check Lock 雙端檢鎖機(jī)制)
? ? public static SingletonDemo getInstance(){
? ? ? ? if(instance == null){
? ? ? ? ? ? synchronized (SingletonDemo.class){
? ? ? ? ? ? ? ? if(instance == null){
? ? ? ? ? ? ? ? ? ? instance = new SingletonDemo();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
? ? }
? ? public static void main(String[] args) {
? ? ? ? for (int i = 1; i <= 10; i++) {
? ? ? ? ? ? new Thread(()->{
? ? ? ? ? ? ? ? SingletonDemo.getInstance();
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? }
}
number++在多線程下是非線程安全的郑什,如何不加synchronized解決?
使用我們的JUC下AtomicInteger的getAndIncrement方法
CAS你知道嗎蒲肋?
CAS(CompareAndSwap)比較并交換
比較當(dāng)前工作內(nèi)存中的值和主內(nèi)存中的值蘑拯,如果相同則執(zhí)行規(guī)定操作钝满,否則繼續(xù)向比較直到主內(nèi)存和工作內(nèi)存中的值一致為止
案例:
package com.example.demo;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 比較并交換
*/
public class CASDemo {
? ? public static void main(String[] args) {
? ? ? ? AtomicInteger atomicInteger = new AtomicInteger(5);
????????//main do ting......
? ? ? ? System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data: " + atomicInteger.get());
? ? ? ? System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data: " + atomicInteger.get());
? ? }
}
CAS底層原理?如果知道申窘,談?wù)勀銓?duì)UnSafe的理解弯蚜?
CAS的操作依賴于Unsafe類的方法,Unsafe類存在于sun.misc包中偶洋,其內(nèi)部方法操作可以像C的指針一樣直接操作內(nèi)存
Unsafe類中的所有方法都是native修飾的熟吏,也就是說(shuō)Unsafe類中的方法都直接調(diào)用操作系統(tǒng)底層資源執(zhí)行相應(yīng)任務(wù)
CAS的全稱為Compare And Swap,它是一條CPU并發(fā)原語(yǔ)玄窝。調(diào)用Unsafe類中的CAS方法牵寺,JVM會(huì)幫我們實(shí)現(xiàn)出CAS匯編指令。這是一種完全依賴于硬件的功能恩脂,通過(guò)它實(shí)現(xiàn)了原子操作帽氓。
由于CAS是一種系統(tǒng)原語(yǔ),原語(yǔ)屬于操作系統(tǒng)用語(yǔ)范圍俩块,是由若干條指令組成的黎休,用于完成某個(gè)功能的一個(gè)過(guò)程,并且原語(yǔ)的執(zhí)行必須是連續(xù)的玉凯,在執(zhí)行過(guò)程中不允許被中斷势腮,也就是說(shuō)CAS是一條CPU的原子指令,不會(huì)造成所謂的數(shù)據(jù)不一致性漫仆。
Unsafe類中CAS(CompareAndSwapInt)捎拯,是一個(gè)本地方法,該方法的實(shí)現(xiàn)位于unsafe.cpp中
CAS缺點(diǎn)盲厌?
循環(huán)時(shí)間長(zhǎng)開(kāi)銷很大(Unsafe類getAndAddInt方法有自旋鎖署照,如果CAS失敗,會(huì)一直進(jìn)行嘗試吗浩。如果CAS長(zhǎng)時(shí)間一直不成功徘溢,會(huì)給CPU帶來(lái)很大的開(kāi)銷)
只能保證一個(gè)共享變量的原子操作(當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí)领虹,我們可以使用循環(huán)CAS的方式來(lái)保證原子操作 但是 對(duì)多個(gè)共享變量操作時(shí)降允,循環(huán)CAS就無(wú)法保證操作的原子性嘹锁,這個(gè)時(shí)候就可以用鎖來(lái)保證原子性)
引出來(lái)ABA問(wèn)題
原子類AtomicInteger的ABA問(wèn)題談?wù)劊吭痈乱弥绬幔?br>
CAS會(huì)導(dǎo)致“ABA問(wèn)題”
CAS算法實(shí)現(xiàn)一個(gè)重要前提需要取出內(nèi)存中某時(shí)刻的數(shù)據(jù)并在當(dāng)下時(shí)刻比較并替換阀湿,那么在這個(gè)時(shí)間差類會(huì)導(dǎo)致數(shù)據(jù)的變化
比如說(shuō)一個(gè)線程one從內(nèi)存位置V中取出A屡限,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出A,并且線程two進(jìn)行了一些操作將值變成了B炕倘,然后線程two又將V位置的數(shù)據(jù)變成A,這時(shí)候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A翰撑,然后線程one操作成功
盡管線程one的CAS操作成功罩旋,但是不代表這個(gè)過(guò)程就是沒(méi)有問(wèn)題的啊央,這個(gè)問(wèn)題可以使用時(shí)間戳原子引用AtomicStampedReference類的getStamp()方法解決。
原子引用(AtomicReference)案例:
package com.example.demo;
import java.util.concurrent.atomic.AtomicReference;
class User{
? ? String userName;
? ? int age;
? ? public User(String userName, int age) {
? ? ? ? this.userName = userName;
? ? ? ? this.age = age;
? ? }
? ? @Override
? ? public String toString() {
? ? ? ? return "User{" +
? ? ? ? ? ? ? ? "userName='" + userName + '\'' +
? ? ? ? ? ? ? ? ", age=" + age +
? ? ? ? ? ? ? ? '}';
? ? }
}
public class AtomicReferenceDemo {
? ? public static void main(String[] args) {
? ? ? ? User z3 = new User("z3", 22);
? ? ? ? User li4 = new User("li4", 25);
? ? ? ? AtomicReference<User> atomicReference = new AtomicReference<>();
? ? ? ? atomicReference.set(z3);
? ? ? ? System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
? ? ? ? System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
? ? }
}
時(shí)間戳原子引用(AtomicStampedReference)實(shí)現(xiàn)ABA問(wèn)題和解決ABA問(wèn)題案例:
package com.example.demo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo { //ABA問(wèn)題解決 AtomicStampedReference
? ? static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
? ? static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
? ? public static void main(String[] args) {
? ? ? ? System.out.println("======================================以下是ABA問(wèn)題的產(chǎn)生=====================================");
? ? ? ? new Thread(()->{
? ? ? ? ? ? atomicReference.compareAndSet(100, 101);
? ? ? ? ? ? atomicReference.compareAndSet(101, 100);
? ? ? ? }, "t1").start();
? ? ? ? new Thread(()->{
? ? ? ? ? ? //暫停1秒鐘t2線程涨醋,保證上面的t1線程完成了一次ABA操作
? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
? ? ? ? }, "t2").start();
? ? ? ? //暫停一會(huì)兒線程
? ? ? ? try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? System.out.println("======================================以下是ABA問(wèn)題的解決=====================================");
? ? ? ? new Thread(()->{
? ? ? ? ? ? int stamp = atomicStampedReference.getStamp();
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 第1次版本號(hào):" + stamp);
? ? ? ? ? ? //暫停1秒鐘t3線程
? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 第2次版本號(hào):" + atomicStampedReference.getStamp());
? ? ? ? ? ? atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 第3次版本號(hào):" + atomicStampedReference.getStamp());
? ? ? ? }, "t3").start();
? ? ? ? new Thread(()->{
? ? ? ? ? ? int stamp = atomicStampedReference.getStamp();
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 第1次版本號(hào):" + stamp);
? ? ? ? ? ? //暫停3秒鐘t4線程
? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 當(dāng)前最新實(shí)際版本號(hào):" + atomicStampedReference.getStamp());
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 當(dāng)前實(shí)際最新值:" + atomicStampedReference.getReference());
? ? ? ? }, "t4").start();
? ? }
}
我們知道ArrayList是線程不安全瓜饥,請(qǐng)編碼寫一個(gè)不安全的案例并給出解決方案?
解決ArrayList線程不安全問(wèn)題:
1.使用new Vector<>();
2.使用Collections.synchronizedList();
ArrayList線程不安全問(wèn)題案例:
package com.example.demo;
import java.util.*;
/**
* 集合類不安全的問(wèn)題
* ArrayList
*/
public class ContainerNotSafeDemo {
? ? public static void main(String[] args) {
? ? ? ? List<String> list = new ArrayList<>();
? ? ? ? for (int i = 1; i <= 30; i++) {
? ? ? ? ? ? new Thread(()->{
? ? ? ? ? ? ? ? list.add(UUID.randomUUID().toString().substring(0,8));
? ? ? ? ? ? ? ? System.out.println(list);
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? ? ? /**
? ? ? ? * 1 故障現(xiàn)象
? ? ? ? * java.util.ConcurrentModificationException
? ? ? ? *
? ? ? ? * 2 導(dǎo)致原因
????????* 并發(fā)爭(zhēng)搶修改導(dǎo)致浴骂,參考我們的花名冊(cè)簽名情況乓土。?
????????* 一個(gè)人正在寫入,另外一個(gè)同學(xué)過(guò)來(lái)?yè)寠Z溯警,導(dǎo)致數(shù)據(jù)不一致異常趣苏,并發(fā)修改異常。
? ? ? ? *
? ? ? ? * 3 解決方案
? ? ? ? * new Vector<>();
? ? ? ? * Collections.synchronizedList(new ArrayList<>());
? ? ? ? */
? ? }
}
不用Vector和Collections工具類解決:
解決集合類不安全之List
new CopyOnWriteArrayList();
解決集合類不安全之Set
new CopyOnWriteArraySet();
解決集合類不安全之Map
new ConcurrentHashMap();
公平鎖和非公平鎖梯轻?
公平鎖是指多個(gè)線程按照申請(qǐng)鎖的順序來(lái)獲取鎖食磕,類似排隊(duì)打飯,先來(lái)后到喳挑。
非公平鎖是指多個(gè)線程獲取鎖的順序并不是按照申請(qǐng)鎖的順序彬伦,有可能后申請(qǐng)的線程比先申請(qǐng)的線程優(yōu)先獲取鎖。在高并發(fā)的情況下伊诵,有可能會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象
關(guān)于兩者區(qū)別:
公平鎖单绑,在并發(fā)環(huán)境中,每個(gè)線程在獲取鎖時(shí)會(huì)先查看此鎖維護(hù)的等待隊(duì)列曹宴,如果為空搂橙,或者當(dāng)前線程是等待隊(duì)列的第一個(gè),就占有鎖浙炼,否則就會(huì)加入到等待隊(duì)列中份氧,以后會(huì)按照FIFO的規(guī)則從隊(duì)列中取到自己
非公平鎖比較粗魯,上來(lái)就直接嘗試占有鎖弯屈,如果嘗試失敗蜗帜,就再采用類似公平鎖那種方式
Java ReentrantLock而言,通過(guò)構(gòu)造函數(shù)指定該鎖是否是公平鎖资厉,默認(rèn)是非公平鎖厅缺。非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大對(duì)于Synchronized而言,也是一種非公平鎖
可重入鎖宴偿?
可重入鎖(又名遞歸鎖)在同一個(gè)線程再外層方法獲取鎖的時(shí)候湘捎,再進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖
ReentrantLock/Synchronized就是一個(gè)典型的可重入鎖
可重入鎖最大的作用是避免死鎖
Synchronized可重入鎖案例:
package com.example.demo;
class Phone{
? ? public synchronized void sendSMS() throws Exception{
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
? ? ? ? sendEmail();
? ? }
? ? public synchronized void sendEmail() throws Exception{
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
? ? }
}
public class ReentrantLockDemo {
? ? public static void main(String[] args) {
? ? ? ? Phone phone = new Phone();
? ? ? ? new Thread(()->{
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? phone.sendSMS();
? ? ? ? ? ? }catch (Exception e){
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }, "t1").start();
? ? ? ? new Thread(()->{
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? phone.sendSMS();
? ? ? ? ? ? }catch (Exception e){
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }, "t2").start();
? ? }
}
ReentrantLock可重入鎖案例:
package com.example.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone implements Runnable{
? ? Lock lock = new ReentrantLock();
? ? @Override
? ? public void run() {
? ? ? ? get();
? ? }
? ? public void get() {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t invoked get()");
? ? ? ? ? ? set();
? ? ? ? }finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
? ? public void set() {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t invoked set()");
? ? ? ? }finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
}
public class ReentrantLockDemo {
? ? public static void main(String[] args) {
? ? ? ? Phone phone = new Phone();
? ? ? ? Thread t3 = new Thread(phone,"t3");
? ? ? ? Thread t4 = new Thread(phone,"t4");
? ? ? ? t3.start();
? ? ? ? t4.start();
? ? }
}
自旋鎖?
是指嘗試獲取鎖的線程不會(huì)立即阻塞窄刘,而是采用循環(huán)的方式嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗窥妇,缺點(diǎn)是循環(huán)會(huì)消耗CPU。
自選鎖案例:
package com.example.demo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
? ? //原子引用線程娩践,引用類型初始值null
? ? AtomicReference<Thread> atomicReference = new AtomicReference<>();
? ? public static void main(String[] args) {
? ? ? ? SpinLockDemo spinLockDemo = new SpinLockDemo();
? ? ? ? new Thread(() -> {
? ? ? ? ? ? spinLockDemo.myLock();
? ? ? ? ? ? //暫停一會(huì)兒線程
? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? spinLockDemo.myUnlock();
? ? ? ? }, "AA").start();
? ? ? ? try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? new Thread(() -> {
? ? ? ? ? ? spinLockDemo.myLock();
? ? ? ? ? ? spinLockDemo.myUnlock();
? ? ? ? }, "BB").start();
? ? }
? ? public void myUnlock() {
? ? ? ? Thread thread = Thread.currentThread();
? ? ? ? atomicReference.compareAndSet(thread, null);
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
? ? }
? ? public void myLock() {
? ? ? ? Thread thread = Thread.currentThread();
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t come in O(∩_∩)O");
? ? ? ? while (!atomicReference.compareAndSet(null, thread)) {
? ? ? ? }
? ? }
}
獨(dú)占鎖(寫)活翩,共享鎖(讀)烹骨,互斥鎖?
獨(dú)占鎖:指該鎖一次只能被一個(gè)線程所持有材泄。對(duì)ReentrantLock和Synchronized而言都是獨(dú)占鎖
共享鎖:指該鎖可被多個(gè)線程所持有沮焕。對(duì)ReentrantReadWriteLock其讀鎖是共享鎖,其寫鎖是獨(dú)占鎖
互斥鎖:在一個(gè)線程修改變量時(shí)加鎖拉宗,則其他變量阻塞峦树,等待加鎖的變量解鎖后再執(zhí)行
主要使用JUC下的ReentrantReadWriteLock讀寫鎖類的writeLock() readLock()方法實(shí)現(xiàn)
以上三個(gè)鎖也可合稱為讀寫鎖
讀寫鎖讀寫沖突問(wèn)題案例:
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
class MyCache {
? ? private volatile Map<String, Object> map = new HashMap();
? ? public void put(String key, Object value) {
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 正在寫入:" + key);
? ? ? ? //暫停一會(huì)兒線程 模擬網(wǎng)絡(luò)擁堵
? ? ? ? try {
? ? ? ? ? ? TimeUnit.MILLISECONDS.sleep(300);
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? map.put(key, value);
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 寫入完成");
? ? }
? ? public void get(String key) {
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 正在讀取:" + key);
? ? ? ? //暫停一會(huì)兒線程 模擬網(wǎng)絡(luò)擁堵
? ? ? ? try {
? ? ? ? ? ? TimeUnit.MILLISECONDS.sleep(300);
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? Object result = map.get(key);
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 讀取完成:" + result);
? ? }
}
public class ReadWriteLockDemo {
? ? public static void main(String[] args) {
? ? ? ? MyCache myCache = new MyCache();
? ? ? ? //5個(gè)線程寫
? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? final int tempInt = i;
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? myCache.put(tempInt + "", tempInt + "");
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? ? ? //5個(gè)線程讀
? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? final int tempInt = i;
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? myCache.get(tempInt + "");
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? }
}
加入ReentrantReadWriteLock解決讀寫沖突問(wèn)題:
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache {
? ? private volatile Map<String, Object> map = new HashMap();
? ? private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
? ? public void put(String key, Object value) {
? ? ? ? rwLock.writeLock().lock();
? ? ? ? try {
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 正在寫入:" + key);
? ? ? ? ? ? //暫停一會(huì)兒線程 模擬網(wǎng)絡(luò)擁堵
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.MILLISECONDS.sleep(300);
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? map.put(key, value);
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 寫入完成");
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? rwLock.writeLock().unlock();
? ? ? ? }
? ? }
? ? public void get(String key) {
? ? ? ? rwLock.readLock().lock();
? ? ? ? try {
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 正在讀鹊┦隆:" + key);
? ? ? ? ? ? //暫停一會(huì)兒線程 模擬網(wǎng)絡(luò)擁堵
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.MILLISECONDS.sleep(300);
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? Object result = map.get(key);
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 讀取完成:" + result);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? rwLock.readLock().unlock();
? ? ? ? }
? ? }
}
public class ReadWriteLockDemo {
? ? public static void main(String[] args) {
? ? ? ? MyCache myCache = new MyCache();
? ? ? ? //5個(gè)線程寫
? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? final int tempInt = i;
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? myCache.put(tempInt + "", tempInt + "");
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? ? ? //5個(gè)線程讀
? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? final int tempInt = i;
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? myCache.get(tempInt + "");
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? }
}
CountDownLatch魁巩?
讓一些線程阻塞直到另一些線程完成一系列操作后才被喚醒
CountDownLatch主要有兩個(gè)方法,當(dāng)一個(gè)或多個(gè)線程調(diào)用await方法時(shí)族檬,調(diào)用線程會(huì)被阻塞歪赢。
其他線程調(diào)用countDown方法會(huì)將計(jì)數(shù)器減1(調(diào)用countDown方法的線程不會(huì)阻塞)。
當(dāng)計(jì)數(shù)器的值變?yōu)?時(shí)单料,因調(diào)用await方法被阻塞的線程會(huì)被喚醒埋凯,繼續(xù)執(zhí)行。
案例:
CountDownLatchDemo.java
package com.example.demo;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
? ? public static void main(String[] args) throws Exception {
? ? ? ? CountDownLatch countDownLatch = new CountDownLatch(6);
? ? ? ? for (int i = 1; i <= 6; i++) {
? ? ? ? ? ? new Thread(()->{
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 國(guó)扫尖,被滅");
? ? ? ? ? ? ? ? countDownLatch.countDown();
? ? ? ? ? ? }, CountryEnum.forEach_countryEnum(i).getRetMessage()).start();
? ? ? ? }
? ? ? ? countDownLatch.await();
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t **********秦國(guó)一統(tǒng)華夏");
? ? }
}
CountryEnum.java
package com.example.demo;
public enum CountryEnum {
? ? ONE(1, "齊"),
? ? TWO(2, "楚"),
? ? THREE(3, "燕"),
? ? FOUR(4, "趙"),
? ? FIVE(5, "魏"),
? ? SIX(6, "韓");
? ? private Integer retCode;
? ? private String retMessage;
? ? public Integer getRetCode() {
? ? ? ? return retCode;
? ? }
? ? public String getRetMessage() {
? ? ? ? return retMessage;
? ? }
? ? CountryEnum(Integer retCode, String retMessage) {
? ? ? ? this.retCode = retCode;
? ? ? ? this.retMessage = retMessage;
? ? }
? ? public static CountryEnum forEach_countryEnum(int index) {
? ? ? ? CountryEnum[] myArray = CountryEnum.values();
? ? ? ? for (CountryEnum element : myArray) {
? ? ? ? ? ? if (index == element.getRetCode()) {
? ? ? ? ? ? ? ? return element;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return null;
? ? }
}
CyclicBarrier白对?
CyclicBarrier的字面意思是可循環(huán)(Cyclic)使用的屏障(Barrier)。它要做的事情是换怖,
讓一組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞甩恼,直到最后一個(gè)線程到達(dá)屏障時(shí),
屏障才會(huì)開(kāi)門沉颂,所有被屏障攔截的線程才會(huì)繼續(xù)干活条摸,線程進(jìn)入屏障通過(guò)CyclicBarrier的await()方法。
案例:
package com.example.demo;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
? ? public static void main(String[] args) {
? ? ? ? CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
? ? ? ? ? ? System.out.println("********召喚神龍");
? ? ? ? });
? ? ? ? for (int i = 1; i <= 7; i++) {
? ? ? ? ? ? final int tempInt = i;
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 收集到第:" + tempInt + "顆龍珠");
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? cyclicBarrier.await();
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? } catch (BrokenBarrierException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? }
}
Semaphore铸屉?
主要用于兩個(gè)目的钉蒲,一個(gè)是用于多個(gè)共享資源的互斥使用,另一個(gè)用于并發(fā)線程數(shù)的控制彻坛。
案例:
package com.example.demo;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
? ? public static void main(String[] args) {
? ? ? ? Semaphore semaphore = new Semaphore(3);//模擬3個(gè)停車位
? ? ? ? for (int i = 1; i <= 6; i++) {//模擬6部汽車
? ? ? ? ? ? new Thread(() -> {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? semaphore.acquire();
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t 搶到車位");
? ? ? ? ? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+ "\t 停車3秒后離開(kāi)車位");
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }finally {
? ? ? ? ? ? ? ? ? ? semaphore.release();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }, String.valueOf(i)).start();
? ? ? ? }
? ? }
}
阻塞隊(duì)列顷啼?
當(dāng)阻塞隊(duì)列是空時(shí),從隊(duì)列中獲取元素的操作將會(huì)被阻塞昌屉。
當(dāng)阻塞隊(duì)列是滿時(shí)钙蒙,往隊(duì)列中添加元素的操作將會(huì)被阻塞。
在多線程領(lǐng)域:所謂阻塞间驮,在某些情況下會(huì)掛起線程(即阻塞)躬厌,一旦條件滿足,被掛起的線程又會(huì)自動(dòng)被喚醒
阻塞隊(duì)列 (BlockingQueue)
為什么需要BlockingQueue竞帽?
好處是我們不需要關(guān)心什么時(shí)候需要阻塞線程烤咧,什么時(shí)候需要喚醒線程偏陪,因?yàn)檫@一切BlockingQueue都給你一手包辦了
在conciurrent發(fā)布以前,在多線程環(huán)境下煮嫌,我們每個(gè)程序員都必須去自己控制這些細(xì)節(jié),尤其還要兼顧效率和線程安全抱虐,而這會(huì)給我們的重新帶來(lái)不小的復(fù)雜度昌阿。
BlockingQueue的核心方法?
拋出異常:
1)當(dāng)阻塞隊(duì)列滿時(shí)恳邀,再往隊(duì)列里add插入元素會(huì)拋出異常IIIegalStateException: Queue full
2)當(dāng)阻塞隊(duì)列空時(shí)懦冰,再往隊(duì)列里remove移除元素會(huì)拋異常NoSuchElementException
3)當(dāng)隊(duì)列為空時(shí), element會(huì)拋出一個(gè)異常
特殊值:
1)offer插入方法谣沸,成功true失敗false
2)poll移除方法刷钢,成功返回出隊(duì)列的元素,隊(duì)列里沒(méi)有就返回null
3)當(dāng)隊(duì)列為空時(shí)乳附,peek返回null
一直阻塞:
1)當(dāng)阻塞隊(duì)列滿時(shí)内地,生產(chǎn)者線程繼續(xù)往隊(duì)列里put元素,隊(duì)列會(huì)一直阻塞生產(chǎn)線程直到put數(shù)據(jù)or響應(yīng)中斷退出
2)當(dāng)阻塞隊(duì)列空時(shí)赋除,消費(fèi)者線程試圖從隊(duì)列里take元素阱缓,隊(duì)列會(huì)一直阻塞消費(fèi)者線程直到隊(duì)列可用
超時(shí)退出:
1)當(dāng)阻塞隊(duì)列滿時(shí),隊(duì)列會(huì)阻塞生產(chǎn)者線程一定時(shí)間举农,超過(guò)限時(shí)后生產(chǎn)者線程會(huì)退出
BlockingQueue架構(gòu)荆针?
Collection
->
Queue
->
BlockingQueue
->
LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列。
LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列颁糟。
PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列航背。
SynchronousQueue:不存儲(chǔ)元素的阻塞隊(duì)列,也即單個(gè)元素的隊(duì)列棱貌。
DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的延遲無(wú)界阻塞隊(duì)列玖媚。
ArrayBlockingQueue:由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界(但大小默認(rèn)值位Integer.MAX_VALUE)阻塞隊(duì)列
ArrayBlockingQueue / LinkedBlockingQueue使用键畴?
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue();
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue();
然后參考 BlockingQueue的核心方法 來(lái)使用
SynchronousQueue最盅?
與其他BlockingQueue不同,SynchronousQueue是一個(gè)不存儲(chǔ)元素的BlockingQueue
每一個(gè)put操作必須要等待一個(gè)take操作起惕,否則不能繼續(xù)添加元素涡贱,反之亦然。
案例:
package com.example.demo;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
? ? public static void main(String[] args) {
? ? ? ? BlockingQueue<String> blockingQueue = new SynchronousQueue();
? ? ? ? new Thread(()->{
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t put 1");
? ? ? ? ? ? ? ? blockingQueue.put("1");
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t put 2");
? ? ? ? ? ? ? ? blockingQueue.put("2");
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t put 3");
? ? ? ? ? ? ? ? blockingQueue.put("3");
? ? ? ? ? ? }catch (Exception e){
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? },"AAA").start();
? ? ? ? new Thread(()->{
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
? ? ? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
? ? ? ? ? ? ? ? try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
? ? ? ? ? ? }catch (Exception e){
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? },"BBB").start();
? ? }
}
JUC1.0和2.0惹想?
synchronized被lock替代
wait被await替代
notify被sinqal替代
阻塞隊(duì)列用在哪问词?
生產(chǎn)者消費(fèi)者模式
線程池
消息中間件
傳統(tǒng)版阻塞隊(duì)列(生產(chǎn)者消費(fèi)者)實(shí)現(xiàn)?
題目:一個(gè)初始值為零的變量嘀粱,兩個(gè)線程對(duì)其交替操作激挪,一個(gè)加1一個(gè)減1辰狡,來(lái)5輪 案例:
package com.example.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData {
? ? private int number = 0;
? ? private Lock lock = new ReentrantLock();
? ? private Condition condition = lock.newCondition();
? ? public void increment() throws Exception {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? //1 判斷
? ? ? ? ? ? while (number != 0) {
? ? ? ? ? ? ? ? //等待,不能生產(chǎn)
? ? ? ? ? ? ? ? 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 decrement() throws Exception {
? ? ? ? lock.lock();
? ? ? ? try {
? ? ? ? ? ? //1 判斷
? ? ? ? ? ? while (number == 0) {
? ? ? ? ? ? ? ? //等待垄分,不能生產(chǎn)
? ? ? ? ? ? ? ? condition.await();
? ? ? ? ? ? }
? ? ? ? ? ? //2 干活
? ? ? ? ? ? number--;
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t" + number);
? ? ? ? ? ? //3 通知喚醒
? ? ? ? ? ? condition.signalAll();
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
}
/**
* 題目:一個(gè)初始值為零的變量宛篇,兩個(gè)線程對(duì)其交替操作,一個(gè)加1一個(gè)減1薄湿,來(lái)5輪
*/
public class ProdConsumer_TraditionDemo {
? ? public static void main(String[] args) {
? ? ? ? ShareData shareData = new ShareData();
? ? ? ? new Thread(() -> {
? ? ? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? shareData.increment();
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }, "AA").start();
? ? ? ? new Thread(() -> {
? ? ? ? ? ? for (int i = 1; i <= 5; i++) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? shareData.decrement();
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }, "BB").start();
? ? }
}
JUC3.0版?zhèn)鹘y(tǒng)版阻塞隊(duì)列(生產(chǎn)者消費(fèi)者)實(shí)現(xiàn)叫倍?
package com.example.demo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyResource {
? ? private volatile boolean FLAG = true;? //默認(rèn)開(kāi)啟,進(jìn)行生產(chǎn)+消費(fèi)
? ? private AtomicInteger atomicInteger = new AtomicInteger();
? ? BlockingQueue<String> blockingQueue = null;
? ? public MyResource(BlockingQueue blockingQueue) {
? ? ? ? this.blockingQueue = blockingQueue;
? ? ? ? System.out.println(blockingQueue.getClass().getName());
? ? }
? ? public void MyProd() throws Exception {
? ? ? ? String data = null;
? ? ? ? boolean retValue;
? ? ? ? while (FLAG) {
? ? ? ? ? ? data = atomicInteger.incrementAndGet() + "";
? ? ? ? ? ? retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
? ? ? ? ? ? if (retValue) {
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 插入隊(duì)列" + data + "成功");
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 插入隊(duì)列" + data + "失敗");
? ? ? ? ? ? }
? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);
? ? ? ? }
? ? ? ? System.out.println(Thread.currentThread().getName() + "\t 大老板叫停了豺瘤,表示FLAG=false吆倦,生產(chǎn)動(dòng)作結(jié)束");
? ? }
? ? public void MyConsumer() throws Exception {
? ? ? ? String result = null;
? ? ? ? while (FLAG) {
? ? ? ? ? ? result = blockingQueue.poll(2L, TimeUnit.SECONDS);
? ? ? ? ? ? if (null == result || result.equalsIgnoreCase("")) {
? ? ? ? ? ? ? ? FLAG = false;
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t 超過(guò)2秒鐘沒(méi)有取到蛋糕,消費(fèi)退出");
? ? ? ? ? ? ? ? System.out.println();
? ? ? ? ? ? ? ? System.out.println();
? ? ? ? ? ? ? ? System.out.println();
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t 消費(fèi)隊(duì)列蛋糕"+result+"成功");
? ? ? ? }
? ? }
? ? public void stop() throws Exception{
? ? ? ? this.FLAG = false;
? ? }
}
public class ProdConsumer_BlockQueueDemo {
? ? public static void main(String[] args) throws Exception {
? ? ? ? MyResource myResource = new MyResource(new ArrayBlockingQueue(10));
? ? ? ? new Thread(()->{
? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t 生產(chǎn)線程啟動(dòng)");
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? myResource.MyProd();
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? },"Prod").start();
? ? ? ? new Thread(()->{
? ? ? ? ? ? System.out.println(Thread.currentThread().getName()+"\t 消費(fèi)線程啟動(dòng)");
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? myResource.MyConsumer();
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? },"Consumer").start();
? ? ? ? try {TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
? ? ? ? System.out.println();
? ? ? ? System.out.println();
? ? ? ? System.out.println();
? ? ? ? System.out.println("5秒鐘時(shí)間到坐求,大老板main線程叫停蚕泽,活動(dòng)結(jié)束");
? ? ? ? myResource.stop();
? ? }
}
Synchronized和Lock有什么區(qū)別?用新的Lock有什么好處桥嗤?舉例說(shuō)明
1)synchronized是關(guān)鍵字须妻。Lock是具體類。
2)synchronized不需要用戶手動(dòng)去釋放鎖砸逊。 ReentrantLock需要用戶去手動(dòng)釋放鎖 lock() 和 unlock() 方法璧南。
3)synchronized執(zhí)行過(guò)程中不可中斷,除非拋出異呈σ荩或運(yùn)行完成司倚。?ReentrantLock可中斷 方法一:設(shè)置超時(shí)方法 tryLock() 方法二:lockInterruptibly() 放到代碼塊中調(diào)用 interrupt() 方法可中斷
4)synchronized是非公平鎖。ReentrantLock默認(rèn)非公平鎖 ReentrantLock(true)后可以改為公平鎖
5)synchronized沒(méi)有Condition篓像。?ReentrantLock可以使用Condition來(lái)實(shí)現(xiàn)精確喚醒線程动知,而不像synchronized要么隨機(jī)喚醒一個(gè)線程要么喚醒全部線程
題目:多線程之間按順序調(diào)用,實(shí)現(xiàn)A->B->C三個(gè)線程啟動(dòng)员辩,要求如下:
AA打印5次盒粮,BB打印10次,CC打印15次
緊接著
AA打印5次奠滑,BB打印10次丹皱,CC打印15次
......
來(lái)10輪
package com.example.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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();
? ? ? ? }
? ? }
}
/**
* 題目:多線程之間按順序調(diào)用,實(shí)現(xiàn)A->B->C三個(gè)線程啟動(dòng)宋税,要求如下:
* AA打印5次摊崭,BB打印10次,CC打印15次
* 緊接著
* AA打印5次杰赛,BB打印10次呢簸,CC打印15次
* ......
* 來(lái)10輪
*/
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();
? ? }
}
鏈表結(jié)構(gòu)與數(shù)組結(jié)構(gòu)?
不同:鏈表是鏈?zhǔn)降拇鎯?chǔ)結(jié)構(gòu);數(shù)組是順序的存儲(chǔ)結(jié)構(gòu)。鏈表通過(guò)指針來(lái)連接元素與元素根时,數(shù)組則是把所有元素按次序依次存儲(chǔ)瘦赫。鏈表的插入刪除元素相對(duì)數(shù)組較為簡(jiǎn)單,不需要移動(dòng)元素蛤迎,且較為容易實(shí)現(xiàn)長(zhǎng)度擴(kuò)充确虱,但是尋找某個(gè)元素較為困難;數(shù)組尋找某個(gè)元素較為簡(jiǎn)單忘苛,但插入與刪除比較復(fù)雜蝉娜,由于最大長(zhǎng)度需要再編程一開(kāi)始時(shí)指定,故當(dāng)達(dá)到最大長(zhǎng)度時(shí)扎唾,擴(kuò)充長(zhǎng)度不如鏈表方便。