Java併發程式設計--基礎進階高階完整筆記。
這都不知道是第幾次刷狂神的JUC併發程式設計了,從第一次的迷茫到現在比較清晰,算是個大進步了,之前JUC筆記不見了,重新做一套筆記。
參考連結:https://www.bilibili.com/video/BV1B7411L7tE
1.多執行緒--基礎內容
1.Thread狀態
6種:新建、執行、阻塞、等待、超時等待、結束(可點選Thread檢視原始碼)
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
2.Synchronized
- 非公平鎖
- 可重入鎖,
Synchronized:是非公平鎖(不能保證執行緒獲得鎖的順序,即執行緒不會依次排隊去獲取資源,而是爭搶,但是結果一定是正確的),是可重入鎖(已獲得一個鎖,可以再獲得鎖且不會造成死鎖,比如synchronized內部可以再寫個synchronized函式)
/**
* Author: HuYuQiao
* Description: Synchronized實現方式(修飾函式即可)
*/
class TicketSync{
public int number = 50;
//synchronized本質是佇列,鎖
public synchronized void sale(){
if(number > 0) {
System.out.println(Thread.currentThread().getName() + "獲得了第" + number-- +"票");
}
}
}
3.Lock鎖
可重入鎖
公平還是不公平鎖可以設定(預設不公平鎖)
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Lock:加鎖之後必須解鎖,,否則其他執行緒就獲取不到了,所以用
try-catch-finally
包起來。
/**
* Author: HuYuQiao
* Description: Lock實現方式(加鎖、解鎖)
*/
class TicketLock{
Lock lock = new ReentrantLock();
public int number = 50;
public void sale(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "獲得了第" + number-- +"票");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.總結
在不加鎖情況下,多執行緒會爭搶,導致輸出順序、計算結果都會不一致(上面例子結果如果一樣是因為只有3個執行緒,for迴圈即出錯,因為number--這個函式本身不是執行緒安全的),所以就引入了鎖的概念,synchronized,lock保證了輸出順序、計算結果的一致性。
虛假喚醒:在synchronized.wait與lock.condition.await喚醒執行緒時,是從await程式碼之後開始執行,所以為了保證能喚醒執行緒,需要用while語句將程式碼包含起來。
完整程式碼
package com.empirefree.springboot;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import sun.security.krb5.internal.Ticket;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: springboot
* @description: 多執行緒
* @author: huyuqiao
* @create: 2021/06/26 14:26
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class ThreadTest {
/**
* Author: HuYuQiao
* Description: Synchronized實現方式(修飾函式即可)
*/
class TicketSync{
public int number = 50;
//synchronized本質是佇列,鎖
public synchronized void sale(){
System.out.println(Thread.currentThread().getName() + "獲得了第" + number-- +"票");
}
}
/**
* Author: HuYuQiao
* Description: Lock實現方式(加鎖、解鎖)
*/
class TicketLock{
Lock lock = new ReentrantLock();
public int number = 50;
public void sale(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "獲得了第" + number-- +"票");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
@Test
public void testThread() {
// TicketSync ticket = new TicketSync();
TicketLock ticket = new TicketLock();
new Thread( () ->{
for (int i = 0; i < 50; i++) {
ticket.sale();
}
},"ThreadA").start();
new Thread(()->{
for (int i = 0; i < 50; i++) {
ticket.sale();
}
},"ThreadB").start();
new Thread(()->{
for (int i = 0; i < 50; i++) {
ticket.sale();
}
},"ThreadC").start();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
ticket.sale();
}).start();
}
}
}
2.八鎖現象(synchronized、static)
即synchronized、static修飾的函式,執行順序、輸出結果。
結果表明:
1.synchronized修飾的函式:會鎖住物件(可以看成鎖物件中某個方法),看起來程式碼會依次執行,而沒有鎖的方法即不受影響,一來就先執行
2.static synchronized修飾的函式:會鎖住類.class(可以不同物件訪問的都是同一個函式),所以2個物件訪問自己的函式依然還是順序執行.
3.一個有static,一個沒有static:即一個鎖類.class,另一個鎖物件,不管是同一個物件還是不同物件,就都不需要等待了,不會順序執行。
1.synchronized
修飾函式會保證同一物件依次順序執行()
class Phone{
//synchronized
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendSms");
}
public synchronized void call() {
System.out.println("call");
}
public void playGame(){
System.out.println("playGame");
}
}
public static void main(String[] args) {
//Thread--程式碼執行順序問題
Phone phone = new Phone();
new Thread(phone::sendSms, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone::call, "B").start();
new Thread(phone::playGame, "C").start();
}
2.static synchronized
class PhoneStatic{
//static synchronized
public static synchronized void sendSmsStatic() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendSmsStatic");
}
public static synchronized void callStatic() {
System.out.println("callStatic");
}
}
public static void main(String[] args) {
PhoneStatic phoneStatic = new PhoneStatic();
PhoneStatic phoneStatic2 = new PhoneStatic();
new Thread(() ->{
phoneStatic2.sendSmsStatic();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() ->{
phoneStatic2.callStatic();
}, "B").start();
}
3.Java集合--安全性
//集合安全性--list,set,map都非執行緒安全
List<String> list = new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
Map<String, String> objectObjectHashMap = new ConcurrentHashMap<>();
Map<Object, Object> objectObjectHashMap1 = Collections.synchronizedMap(new HashMap<>());
//set底層就是map:無論hashset還是linkedhashset
Set<String> set = Collections.synchronizedSet(new LinkedHashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
4.高併發--輔助類
學習連結:https://www.cnblogs.com/meditation5201314/p/14395972.html
1.countdownLatch
Countdownlatch:減一操作,直到為0再繼續向下執行
package Kuangshen.JUC.Thread;
import java.util.concurrent.CountDownLatch;
public class countDownLatch {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "get out");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待上述執行完畢再向下執行
System.out.println("close door");
}
}
2.cyclicbarrier
Cyclicbarrier:+1操作,對於每個執行緒都自動+1並等待,累計到規定值再向下執行,
package Kuangshen.JUC.Thread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author :Empirefree
* @description:TODO
* @date :2021/2/10 10:56
*/
public class cyclicbarrier {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
//CyclicBarrier裡面是容量 + runnable
final CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () ->{
System.out.println("不斷增加到7即向後執行,與countdownlatch相反");
}
);
/*對於指派的區域性變數,lambda只能捕獲一次 ,故而需定義成final(int內部定義就是final),而且執行緒中,
不能對區域性變數進行修改,如需要修改,需定義成原子類atomic
*/
for (int i = 0; i < 7; i++) {
int finalI = i;
new Thread(() ->{
System.out.println(finalI);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
3.semaphore
semaphore:對於規定的值,多個執行緒只規定有指定的值能獲取,每次獲取都需要最終釋放,保證一定能互斥執行
package Kuangshen.JUC.Thread;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author :Empirefree
* @description:TODO
* @date :2021/2/10 15:24
*/
public class semaphore {
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 60; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "搶到車位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "離開車位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}
5.讀寫鎖(ReadWriteLock)
ReadWriteLock也是多執行緒下的一種加鎖方式,下面列出ReadWriteLock和synchronized對多執行緒下,保證讀完之後在寫的實現方式
//讀寫鎖 :寫只有一個執行緒寫,寫完畢後 可以多個執行緒讀
MyCache myCache = new MyCache();
int num = 6;
for (int i = 1; i < num; i++) {
int finalI = i;
new Thread(()->{
myCache.write(String.valueOf(finalI),String.valueOf(finalI));
},String.valueOf(i)).start();
}
for (int i = 1; i < num; i++) {
int finalI = i;
new Thread(()->{
myCache.read(String.valueOf(finalI));
},String.valueOf(i)).start();
}
class MyCache{
private volatile Map<String,String> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存,寫
public void write(String key,String value){
lock.writeLock().lock(); //寫鎖
try {
System.out.println(Thread.currentThread().getName()+"執行緒開始寫入");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"執行緒開始寫入ok");
} catch (Exception e){
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取,讀
public void read(String key){
lock.readLock().lock(); //讀鎖
try {
System.out.println(Thread.currentThread().getName()+"執行緒開始讀取");
map.get(key);
System.out.println(Thread.currentThread().getName()+"執行緒讀取ok");
} catch (Exception e){
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
//存,寫
public synchronized void writeSync(String key,String value){
try {
System.out.println(Thread.currentThread().getName()+"執行緒開始寫入");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"執行緒開始寫入ok");
} catch (Exception e){
e.printStackTrace();
}
}
//取,讀
public void readSync(String key){
try {
System.out.println(Thread.currentThread().getName()+"執行緒開始讀取");
map.get(key);
System.out.println(Thread.currentThread().getName()+"執行緒讀取ok");
} catch (Exception e){
e.printStackTrace();
}
}
}
6.執行緒池
1.集合--佇列(阻塞佇列、同步佇列)
阻塞佇列:blockingQueue(超時等待--拋棄,所以最後size=1)
//阻塞佇列
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
arrayBlockingQueue.offer("a", 2, TimeUnit.SECONDS);
arrayBlockingQueue.offer("a", 2, TimeUnit.SECONDS);
System.out.println("超時等待==" + arrayBlockingQueue.size());
同步佇列:synchronizeQueue(類似於生產者消費者)
//同步佇列
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 01");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 02");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 03");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
2.執行緒池基本概念(三大方法、七大引數、四種拒絕策略)
三大方法:newSingleThreadExecutro(單個執行緒),newFixedThreadPool(固定大小執行緒池),newCachedThreadPool(可伸縮)
ExecutorService threadPool = Executors.newSingleThreadExecutor();
ExecutorService threadPool2 = Executors.newFixedThreadPool(5);
ExecutorService threadPool3 = Executors.newCachedThreadPool();
七大引數:
ThreadPoolExecutor(int corePoolSize, //核心執行緒池大小
int maximumPoolSize, //最大的執行緒池大小(當阻塞佇列滿了就會開啟)
long keepAliveTime, //(空閒執行緒最大存活時間:即最大執行緒池中有空閒執行緒超過這個時間就會釋放執行緒,避免資源浪費)
TimeUnit unit, //超時單位
BlockingQueue<Runnable> workQueue, //阻塞佇列
ThreadFactory threadFactory, //執行緒工廠 建立執行緒的 一般不用動
RejectedExecutionHandler handler //拒絕策略四種拒絕策略:
new ThreadPoolExecutor.AbortPolicy: // 該 拒絕策略為:銀行滿了,還有人進來,不處理這個人的,並丟擲異常
new ThreadPoolExecutor.CallerRunsPolicy(): // //該拒絕策略為:哪來的去哪裡 main執行緒進行處理
new ThreadPoolExecutor.DiscardPolicy(): //該拒絕策略為:佇列滿了,丟掉異常,不會丟擲異常。
new ThreadPoolExecutor.DiscardOldestPolicy(): //該拒絕策略為:佇列滿了,嘗試去和最早的程序競爭,不會丟擲異常
7.Stream(4個函式式介面、Lambda、非同步回撥)
1.函式式介面
學習連結:https://www.cnblogs.com/meditation5201314/p/13693089.html
2.Lambda表示式
學習連結:https://www.cnblogs.com/meditation5201314/p/13651755.html
3.非同步回撥
//非同步回撥--無返回值
CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "....");
});
System.out.println("begin");
System.out.println(future.get());
System.out.println("end");
//非同步回撥--有返回值
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() ->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 3;
});
System.out.println("begin");
System.out.println(future2.get());
System.out.println(future2.whenComplete((result, error) ->{
System.out.println("返回結果:" + result);
System.out.println("錯誤結果" + error);
}).exceptionally(throwable -> {
System.out.println(throwable.getMessage());
return 502;
}).get());
System.out.println("end");
8.單例模式
1.餓漢模式(程式一啟動就new,十分佔記憶體)
/**
* @program: untitled
* @description: 單例模式
* @author: huyuqiao
* @create: 2021/06/27 14:44
*/
public class SingleModel {
/*
* 可能會浪費空間
* */
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private static final SingleModel hugrySingle = new SingleModel();
private SingleModel(){
}
public static SingleModel getInstance(){
return hugrySingle;
}
}
2.懶漢模式(DCL模式:雙重檢測,需要的時候才new)
1.第一層if沒有加鎖,所以會有多個執行緒到達if
2.如果內部new物件指令重排,就會導致有些執行緒認為lazyManModel有物件,所以會直接返回lazyManModel(實際為null)
3.所以需要加上valitile防止指令重排
/**
* @program: untitled
* @description: 懶漢式
* @author: huyuqiao
* @create: 2021/06/27 15:06
*/
public class LazyManModel {
private volatile static LazyManModel lazyManModel;
private LazyManModel(){
System.out.println(Thread.currentThread().getName() + "...");
}
//DCL懶漢式:雙重檢測鎖--實現效果,只有為空的才null,否則不用null,所以需要2重if判斷。
public static LazyManModel getInstance(){
if (lazyManModel == null){
synchronized (LazyManModel.class){
if (lazyManModel == null){
lazyManModel = new LazyManModel();
}
}
}
return lazyManModel;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() ->{
LazyManModel.getInstance();
}).start();
}
}
}
9.Volatile和Atomic
學習筆記:https://www.cnblogs.com/meditation5201314/p/13707590.html
10.Java中鎖
1.公平鎖(FIFO):Lock鎖可以自定義,synchronized
2.非公平鎖(允許插隊):Lock鎖可以自定義
3.可重入鎖(獲取一個鎖再獲取其他鎖不會造成死鎖):lock鎖和synchronized
4.自旋鎖:得不到就一直等待(Atomic.getAndIncrement底層就是自旋鎖)
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: untitled
* @description: spinlock
* @author: huyuqiao
* @create: 2021/06/27 15:40
*/
public class SpinLockTest {
public static void main(String[] args) throws InterruptedException {
//使用CAS實現自旋鎖
SpinlockDemo spinlockDemo=new SpinlockDemo();
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myUnlock();
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myUnlock();
}
},"t2").start();
}
}
class SpinlockDemo {
// 預設
// int 0
//thread null
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加鎖
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"===> mylock");
//自旋鎖--為空則返回true,否則返回false
while (!atomicReference.compareAndSet(null,thread)){
System.out.println(Thread.currentThread().getName()+" ==> .自旋中~");
}
}
//解鎖
public void myUnlock(){
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"===> myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
5.死鎖命令排查
jps -l
jstack 程序號