1. 程式人生 > 其它 >狂神-JUC筆記

狂神-JUC筆記

### 什麼是JUC

是java.util.concurrent包下的內容

### 執行緒和程序

程序裡面包含有多條執行緒

### Synchronized和Lock區別

1,Synchronized是java的關鍵字,Lock是介面

2,Synchronized不能判斷鎖的狀態,Lock是可以判斷是否獲取鎖

3,Synchronized不需要手動釋放鎖,Lock需要手動釋放鎖

4,Synchronized:非公平鎖,Lock預設是非公平鎖,可以設定成公平鎖

### 生產者和消費者的問題

### 8鎖現象

總結:

```java
首先是判斷鎖的物件是誰,然後看一下是幾把鎖,如果是一把鎖:就是誰先拿到鎖誰就先執行,如果是2把鎖,就是不公平鎖,誰先執行完就是誰
```

### 集合類不安全

List同set集合

```java
/** 併發下ArrayList不安全報錯:ConcurrentModificationException
* 解決方案:
* 1. List<String> arrayList =new Vector<>();
* 2. List<String> arrayList = Collections.synchronizedList(new ArrayList<>());
* 3,List<String> arrayList = new CopyOnWriteArrayList<>();
*/
```

Map

```java
* 1,Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
* 2,Map<String,String> map = new ConcurrentHashMap<>();
```

### Callable

是一個介面,和Runable介面不同的是,Callable介面有返回值,有異常,Runable介面麼有返回值和異常

```java
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {

//FutureTask的本質就是Runnable,FutureTask是一個介面卡,來適配Callable
FutureTask myThreadFutureTask = new FutureTask(new MyThread());
new Thread(myThreadFutureTask,"A").start();
//這裡會有快取,所以執行結果之會輸出一次
new Thread(myThreadFutureTask,"B").start();
//這裡通過FutureTask.get()方法可以獲取到Callable的返回值
Object o = myThreadFutureTask.get();
System.out.println(o);
}
}

class MyThread implements Callable<Integer>{

@Override
public Integer call() throws Exception {
System.out.println("Call()");
return 10;
}
}
```

### 常用輔助類

```
* 輔助類:
* countDownLatch 減法計數器
* CyclicBarrier 加法計數器
* Semaphore 相當於訊號量 這個是可以用來做併發限流的
```

### 讀寫鎖

ReadWriteLock 讀寫鎖

維護一堆關聯的Locks,read lock可以有多個執行緒同時進行,write lock 只有一個

讀鎖也叫 共享鎖(一次可以多個執行緒共享)

寫鎖 也叫獨佔鎖 ,排他鎖,(一次只能是一個執行緒獨有)

### 阻塞佇列

Queue:佇列:阻塞佇列(BlockingQueue)、非阻塞佇列(AbstractQueue)、雙端佇列(Deque)

```java
BlockingQueue`常用的實現類:`ArrayBlockingQueue`,`LinkedBlockingQueue`,`SynchronousQueue
```

```
什麼情況下會使用阻塞佇列:多執行緒併發處理,執行緒池
佇列是先去先出
```

### 執行緒池

```
總結:執行緒可複用,可以控制最大併發數,可以管理執行緒
```

> 執行緒池——三大方法

1. `Executors.newSingleThreadExecutor()`單個執行緒
2. `Executors.newFixedThreadPool(3)`自定義最大執行緒數
3. `Executors.newCachedThreadPool()`可伸縮的

### 四大函式式介面

lambda表示式、函式式介面、stream、鏈式程式設計

只有一個方法的介面,Runable介面,簡化編碼模型,在新版本的框架大量應用

>四大函式式介面:`Consumer`(消費,只有輸入)、`Function`(函式,一個輸入一個輸出)、`Predicate`(斷定,一個輸入,返回boole)、`Supplier`(生成,只有輸出)
>
>```
>list.forEach();消費者的函式式介面
>```
>
>```java
>一、函式式介面原始碼: 一個輸入引數,一個輸出引數
>public interface Function<T, R> {
>R apply(T t);
>
>}
>二、斷定型引數 :有一個引數,返回值是布林型別
> @FunctionalInterface
>public interface Predicate<T> {
>boolean test(T t);
>}
>
>三、生產消費型
>```

### Stream流式計算

大資料:儲存+計算

儲存:集合、Mysql 本質上儲存

計算都應該交給流來操作

Stream介面

```java
public class Demo {
/**
* 一分鐘內完成此題,只能用一行程式碼實現
* 現在有5個使用者,篩選:
* 1. ID 必須是偶數
* 2. 年齡必須大於23歲
* 3. 使用者名稱轉大寫字母
* 4. 使用者名稱字母倒序
* 5. 只輸出一個使用者!
*/
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
//集合就是儲存的
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

//計算交給流
list.stream()
.filter(u->{return u.getId()%2==0;})
.filter(uu->{return uu.getAge()>=23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);

}

}
```

### ForkJoin

在jdk1.7,並行執行任務,提高效率,大資料量

1,分支合併,是將一個大任務,拆分為幾個小任務,然後把這些小任務的結果總和起來,就是思想

大事化小,小事化了

2,特點是 工作竊取:

這個裡面維護的都是雙端佇列

```java
public class ForkJoinTest extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp=10000L; -- 可以優化
public ForkJoinTest(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
Long sum=0L;
if((end-start<temp)){
//正常計算
for (long i = start; i <=end ; i++) {
sum+=i;
}
return sum;
}else {//分支計算
//1,先去求一下中間數
long middle = (end+start) / 2;
ForkJoinTest forkJoinTest1= new ForkJoinTest(start, middle);
ForkJoinTest forkJoinTest2 = new ForkJoinTest(middle+1, end);
forkJoinTest1.fork();//把任務壓入執行緒佇列
forkJoinTest2.fork();
return forkJoinTest1.join()+forkJoinTest2.join();

}
}
}
```

test類

```java
public static void test1() {
Long sum=0L;
long statTime= System.currentTimeMillis();
for (int i = 0; i <=10_0000_0000; i++) {
sum +=i;
}
long endTime= System.currentTimeMillis();
System.out.println("sum="+sum+"時間用了="+(endTime-statTime));

//結果 :sum=500000000500000000時間用了=5603
}
public static void test2() throws ExecutionException, InterruptedException {
long statTime= System.currentTimeMillis();
ForkJoinPool forkJoinPool =new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinTest(0L,10_0000_0000L);
//提交任務
ForkJoinTask<Long> submit = forkJoinPool.submit(task);

Long sum = submit.get();
long endTime= System.currentTimeMillis();
System.out.println("sum="+sum+"時間用了="+(endTime-statTime));
//sum=500000000500000000時間用了=4970
}
public static void test3() {
long statTime= System.currentTimeMillis();
//並行流rangeClosed(】 range()包含不包含的意思
long sum = LongStream.rangeClosed(0, 10_0000_0000L).parallel().reduce(0, Long::sum);
long endTime= System.currentTimeMillis();
System.out.println("sum="+sum+"時間用了="+(endTime-statTime));
//sum=500000000500000000時間用了=275
}
}
```

### 非同步回撥

Futuer:對將來的某個事件的結果進行建模

```java
public static void main(String[] args) throws ExecutionException, InterruptedException {

//1,沒有返回的非同步
// CompletableFuture<Void>future = CompletableFuture.runAsync(() -> {
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("非同步回撥");
//
// });
// System.out.println("我先輸出....");
// //這裡會阻塞等待結果返回
// future.get();

//2,非同步回撥,有返回值
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"supplyAsync==>Integer");
int i=10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((u,t) -> {
//u :這裡如果是正確就會是正確的返回值,如果錯誤就為null
System.out.println("u==>"+u);
//t :如果有錯誤 這裡輸出錯誤 如java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
System.out.println("t==>"+t);
}).exceptionally((e)->{
//如果有錯誤 這裡輸出
System.out.println(e.getMessage());
return 222;
}).get());
}
```

### JMM :

java記憶體模型,約定,如下:

1,執行緒解鎖前,必須把共享變數 立刻 刷回主記憶體

2,執行緒加鎖前,必須讀取主記憶體的最新值到工作記憶體中!

3,加鎖和解鎖必須是同一把鎖

執行緒:工作記憶體、主記憶體,執行引擎

8中、4組操作

read(讀取)load(載入)|use(使用)、assign(重新整理)|write(寫入主記憶體)store(儲存)|lock(加鎖)、unlock(解鎖) 必須成對出現

問題:執行緒B修改了內容,執行緒A不能及時可見----引入volatile

### Volatitle

volatile:是java虛擬機器提供的 輕量級的同步機制

1,保證可見性

2,不保證原子型,

3,禁止指令重排

保證可見性程式碼:

```java
public class JMMDemo {
//不加volatile,執行緒1,不知道內容修改了
private static volatile Integer num=0;
public static void main(String[] args) {
//主執行緒

new Thread(()->{ //執行緒1,對主記憶體的變化不知道
while (num==0){

}
},"A").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
```

不保證原子性:

```java
public class VTest {
//驗證volatile的沒有原子型的特點
// private static volatile int num=0;
private static AtomicInteger num =new AtomicInteger(0);

public synchronized static void add(){
num.getAndIncrement();//num++

}
public static void main(String[] args) {

for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j <10000 ; j++) {
add();
}
}).start();
}
//執行緒數大於2說明沒有執行完,多執行緒下一定不要用if。
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("計算結果="+num);
//執行結果 不是每次100000 所以不能保證原子性

//解決方案:
//1,加鎖,synchronized或者是Lock,如果不用這個那????
//2,使用的是原子包裝型別也能解決這個問題
// 這些類的底層都直接和作業系統掛鉤!在記憶體中修改值,Unsafe類是一個很特殊的存在

// Volatile在單例模式中使用的最多。

}
}
```

防止指令重排

好比你寫的程式碼,計算機並不是按照你寫的程式碼的順序去執行的。

原始碼—->>編譯器優化重排—->>指令並行也可能重排—->>記憶體系統也會重排—->>執行

`volatile`可以避免指令重排

記憶體屏障,CPU指令。作用:

1、保證特定的操作的執行順序!

2、可以保證某些變數的記憶體可見性。(利用這些特性volatile就可以實現可見性)

```
加了volatile關鍵字的操作,都會在上方和下方形成一個屏障來保證程式碼的順序,防止指令重排
```

### 單例模式

餓漢式

```java
public class Hungry {
//浪費記憶體
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 byte[] data5=new byte[1024*1024];

private final static Hungry hungry=new Hungry();

public static Hungry getInstancd(){
return hungry;
}

private Hungry() {
}
}
```

懶漢式 :產生問題:單執行緒是沒有問題的,但多執行緒會出現問題

```java
public class LazyMan {

private LazyMan(){

System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static LazyMan LAZY_MAN ;
public static LazyMan getInstance(){
if (LAZY_MAN==null){
LAZY_MAN=new LazyMan();
}
}
}
return LAZY_MAN;
}
```

雙重校驗鎖(DCL懶漢)

```java
public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan LAZY_MAN ;
//雙層檢測鎖模式DCL懶漢
public static LazyMan getInstance(){
if (LAZY_MAN==null){
synchronized (LazyMan.class){
if (LAZY_MAN==null){
//不是原子操作
/**
* 1.分配記憶體空間
* 2.執行構造方法,初始化物件
* 3,吧這個物件指向這個空間
*
* ------
* 解決:加volatile
*/
LAZY_MAN=new LazyMan();
}
}
}
return LAZY_MAN;
}
//單執行緒執行緒沒有問題
//併發產生多個物件
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
//獲取類物件
Class<LazyMan> lazyManClass = LazyMan.class;
//獲取構造方法
Constructor<LazyMan> constructors = lazyManClass.getDeclaredConstructor();
//打破私有的構造方法
constructors.setAccessible(true);
//建立例項
LazyMan instance2 = constructors.newInstance();
System.out.println(instance);
System.out.println(instance2);
//com.atguigu.java.thread.single.LazyMan@30946e09
//com.atguigu.java.thread.single.LazyMan@5cb0d902
}
}
```

靜態的內部類寫法:

```java
public class Holder {

public Holder getInstance(){
return InnerClass.holder;
}
private Holder(){
}
public static class InnerClass{
private static final Holder holder=new Holder();
}
}
```

### CAS

比較當前工作記憶體中的值和主記憶體中的值,如果這個值是期望的,那麼則執行操作!如果不是就一直迴圈!

java是:compareAndSet

```java
public class Test01 {

public static void main(String[] args) {
//CAS compareAndSet:比較並交換!
AtomicInteger atomicInteger= new AtomicInteger(1);
//如果是期望的值就更新,如果不是就不去更新
System.out.println(atomicInteger.compareAndSet(1,2));

System.out.println(atomicInteger.get());

System.out.println(atomicInteger.compareAndSet(1,3));

System.out.println(atomicInteger.get());
}

}
```

原始碼:

```java
public final boolean compareAndSet(int expect, int update) {
//java無法操作記憶體需要通過native呼叫C++去操作記憶體,Java還可以通過Unsafe這個類去操作記憶體
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}
```

缺點:

1,底層是自旋鎖,迴圈會耗時

2,一次只能保證一個共享變數的原子性

3,ABA問題

ABA問題就像西遊記裡面的真假葫蘆一樣,真的已經被人掉包了,而他自己還傻傻分不清。

```java
public class Test01 {

public static void main(String[] args) {
//CAS compareAndSet:比較並交換!
AtomicInteger atomicInteger= new AtomicInteger(1);
//如果是期望的值就更新,如果不是就不去更新

//這個是 ABA 問題
//搗亂+++++++++++++++++
System.out.println(atomicInteger.compareAndSet(1,2));
System.out.println(atomicInteger.compareAndSet(2,1));
System.out.println(atomicInteger.get());
//正常+++++++++++++++++
System.out.println(atomicInteger.compareAndSet(1,3));

System.out.println(atomicInteger.get());
//解決方法就是原子引用
}
}
```

### 原子引用

帶版本號的原子操作

`AtomicReference`不帶標記的原子引用

`AtomicStampedReference`帶標記的原子引用

> 原子引用可以解決ABA問題,對應的思想就是 ** 樂觀鎖**

```java
public static void main(String[] args) {

//正常業務場景,是一個物件 USer
AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(6, 1);

//CAS會出現一個ABA問題解決的方案是 :使用原子引用 : 帶有版本號的原子操作 :AtomicStampedReference
//如果泛型是包裝類,主要物件的引用問題
new Thread(()->{

System.out.println("A1->"+stampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.compareAndSet(6, 7, stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("A2->"+stampedReference.getStamp());
System.out.println(stampedReference.compareAndSet(7, 6, stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("A3->"+stampedReference.getStamp());

},"A").start();


new Thread(()->{
System.out.println("B1->"+stampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("B2->"+stampedReference.compareAndSet(6, 10, stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("B2->"+stampedReference.getStamp());

},"B").start();

}
列印:
A1->1
B1->1
B2->true
B2->2
false
A2->2
false
A3->2
```

### 鎖的理解

公平鎖:不能插隊

非公平鎖:可以插隊,預設的都是非公平鎖

```java
//設定為公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//設定為非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
```

可重入鎖:是指的獲取到了外面的鎖,就能自動獲得裡面的鎖。

所有的鎖都是可重入鎖,(遞迴鎖)

```java
public class Demo02 {
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
new Thread(()->{
phone1.sms();
},"A").start();

new Thread(()->{
phone1.sms();
},"B").start();
}
}
class Phone2{
//主要的細節點:鎖必須是成對出現的,否則會出現死鎖
Lock lock= new ReentrantLock();

public void sms(){
lock.lock();//加鎖

try {
System.out.println(Thread.currentThread().getName()+"--->sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解鎖
}


}

public void call(){
lock.lock();//加鎖
try {
System.out.println(Thread.currentThread().getName()+"--->call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
```

自旋鎖:會一直執行,直到執行有符合條件的時候才結束

```java
public class MyThread {

public static void main(String[] args) throws Exception{
//自旋鎖的自我實現
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.unLock();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.unLock();
}

},"B").start();
}
}
```

```java
public class SpinLockDemo {
AtomicReference<String> atomicReference = new AtomicReference<String>();
//加鎖
public void lock(){
System.out.println(Thread.currentThread().getName()+"加鎖中");
while (!atomicReference.compareAndSet(null,"哈哈~~")){
}
}
//解鎖
public void unLock(){
System.out.println(Thread.currentThread().getName()+"解鎖中");
atomicReference.compareAndSet("哈哈~~",null);
}
}
```

死鎖:兩個執行緒互相呼叫

```java
public class Deadlock {

public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new Dead(lockA,lockB),"執行緒T1").start();
new Thread(new Dead(lockB,lockA),"執行緒T2").start();
}

}

class Dead implements Runnable {


String lockA;
String lockB;

public Dead(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}

@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName()+"===>"+lockA+"試圖獲取:"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
synchronized (lockB){

}
}
}
}
```

排查死鎖:

1.通過jdk自帶的工具命令`jps -l`排查存活的程序

D:\lei2020\Code2020\cloud2020>jps -l
15952 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
16420 com.atguigu.java.thread.lock8.Deadlock
8740
18120 org.jetbrains.jps.cmdline.Launcher
11660 sun.tools.jps.Jps

2,執行命令:jstack 16420

"執行緒T2" #12 prio=5 os_prio=0 tid=0x0000000019d9d800 nid=0x3ae4 waiting for monitor entry [0x000000001a9be000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.atguigu.java.thread.lock8.Dead.run(Deadlock.java:57)
- waiting to lock <0x00000000d62bc008> (a java.lang.String)
- locked <0x00000000d62bc040> (a java.lang.String)
at java.lang.Thread.run(Thread.java:745)

"執行緒T1" #11 prio=5 os_prio=0 tid=0x0000000019d9b000 nid=0x2098 waiting for monitor entry [0x000000001a8bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.atguigu.java.thread.lock8.Dead.run(Deadlock.java:57)

- waiting to lock <0x00000000d62bc040> (a java.lang.String)
- locked <0x00000000d62bc008> (a java.lang.String)
at java.lang.Thread.run(Thread.java:745)
-

讀鎖(共享鎖) :多個執行緒可以同時操作

寫鎖(獨佔鎖、排他鎖):一個執行緒操作