JUC詳解--【Foam番茄】
1.什麼是JUC
java.util 工具包
業務:普通的執行緒程式碼 Thread
Runnable 沒有返回值,效率相比於 Callable 相對較低!
2.執行緒和程序
程序:一個程式,QQ.exe Music.exe 程式的集合
程序:一個程序往往可以包含多個執行緒,至少包含一個
java預設有幾個執行緒?2個 一個main 一個GC
執行緒:程序中的一個執行任務(控制單元),負責當前程序中程式的執行。一個程序至少有一個執行緒,一個程序可以執行多個執行緒,多個執行緒可共享資料。
與程序不同的是同類的多個執行緒共享程序的堆和方法區資源,但每個執行緒有自己的程式計數器、虛擬機器棧
程序和執行緒的根本區別是程序是作業系統資源分配的基本單位,而執行緒是處理器任務排程和執行的基本單位。 另外區別還有資源開銷、包含關係、記憶體分配、影響關係、執行過程等。
java真的可以開啟執行緒嗎?不可以
本地方法,呼叫底層的c++,java無法直接操作硬體
併發,並行
併發程式設計:併發,並行
併發(多執行緒操作同一個資源)
- CPU一核,模擬出來多條執行緒,天下武功,唯快不破,快速交替
並行(多個人一起行走)
- CPU多核,多個執行緒可以同時執行
- 併發程式設計的本質:充分利用CPU的資源
執行緒有幾個狀態
public enum State {
NEW,//新生
RUNNABLE,//執行
BLOCKED,//阻塞
WAITING,//等待
TIMED_WAITING,//超時等待
TERMINATED;//終止
}
wait/sleep區別
1.來自不同的類
wait=>Object
sleep=>Thread
企業當中,休眠
TimeUnit操作
2.關於鎖的釋放
wait 會釋放鎖
sleep 睡覺了,抱著鎖睡覺,不會釋放鎖
3.使用的範圍是不同的
wait必須在同步程式碼塊中
sleep可以在任何地方睡
4.是否需要捕獲異常
wait不需要捕獲異常
sleep需要捕獲異常
Lock鎖(重點)
執行緒就是一個單獨的資源類,沒有任何附屬的操作
1.屬性,方法
傳統synchronized
Lock介面
公平鎖:十分公平:可以先來後到
非公平鎖:十分不公平:可以插隊(預設)
Synchronized 和 Lock 區別
1.Synchronized 內建的java關鍵字,Lock是一個java類
2.Synchronized 無法判斷獲取鎖的狀態,Lock可以判斷是否獲取到了鎖
3.synchronized 會自動釋放鎖,Lock 必須要手動釋放鎖,如果不釋放鎖就會造成死鎖
4.Synchronized 執行緒1(獲得鎖,阻塞),執行緒2(等待,一直等),Lock鎖就不一定會等待下去,lock.tryLock
5.Synchronized 可重入鎖,不可以中斷的,非公平;Lock,可重入鎖,可以判斷鎖,非公平(可以設定)
6.synchronized 適合鎖少量的程式碼同步問題,Lock適合鎖大量的同步程式碼
鎖是什麼,如何判斷鎖的區別
4.生產者和消費者問題
生產者和消費者問題Synchronized版
面試的:單例模式,排序演算法,生產者和消費者,死鎖
if會造成虛假喚醒,用while可以避免
任何新技術,絕不可能只是覆蓋了原來的技術,有自己的優勢和補充
Condition 精準通知和喚醒執行緒
5.八鎖現象
如何判斷鎖的是誰,永遠的知道什麼是鎖
synchronized 鎖的物件是方法的呼叫者
兩個方法用的是同一個鎖,誰先拿到誰執行
兩個物件,兩把鎖,互相不干擾
new this 具體的一個物件
static Class 唯一的一個模板
6.集合類不安全
List不安全
併發下 ArrayList 不安全的
解決方案:
1.Vector解決 List
2.Collections解決 List
3.juc解決List
多個執行緒呼叫的時候,list,讀取的時候,固定的,寫入的時候可能存在覆蓋操作
在寫入的時候避免覆蓋,造成資料問題
為什麼CopyOnWriteArrayList比Vector效率高,因為Vector使用了Synchronized就會造成效率低,CopyOnWriteArrayList使用的Lock鎖
set不安全
1.Set
2.Set
hashSet底層是什麼?
hashset底層就是hashMap,set的本質就是map的key,key是無法重複的
map是怎樣用的,載入因子,初始化容量
map不安全
工作中不用hashMap
預設等價於
Map<String,String>map=new HashMap<>(16,0.75);
1.Map<String,String> map= Collections.synchronizedMap(new HashMap<>());
2.Map<String,String> map=new ConcurrentHashMap<>();
7.Callable
1.可以有返回值
2.可以丟擲異常
3.方法不同,run()/call()
package com.kuang.demo01.lock;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class callableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// TODO Auto-generated method stub
new Thread().start();
MyThread thread=new MyThread();
FutureTask futureTask=new FutureTask(thread); // 適配類
new Thread(futureTask,"a").start();
Integer o=(Integer) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return 1024;
}
}
1.有快取
2.結果可能需要等待,會阻塞
8.常用的輔助類
CountDownLatch
package com.kuang.demo01.lock;
import java.util.concurrent.CountDownLatch;
// 計數器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 總數是6
CountDownLatch count=new CountDownLatch(6);
for(int i=1;i<=6;i++) {
new Thread(()->{
count.countDown(); // 數量-1
},String.valueOf(i)).start();
}
count.await(); // 等待計數器歸零,然後再向下執行
}
}
原理:
count.countDown(); // 數量-1
count.await(); // 等待計數器歸零,然後再向下執行
每次有執行緒呼叫countDown數量-1,假設計數器變為0,countDownLatch.await就會被喚醒,繼續執行
CyclicBarrier
package com.kuang.demo01.lock;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 召喚龍珠的執行緒
CyclicBarrier cyclic=new CyclicBarrier(7,()->{
System.out.println("召喚神龍成功");
});
for (int i = 0; i < 7; i++) {
final int temp=i;
new Thread(()-> {
System.out.println(temp);
try {
cyclic.await();
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}).start();
}
}
}
Semaphore 訊號量
package com.kuang.demo01.lock;
import java.util.concurrent.Semaphore;
public class SemphoreDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 總共三個位置 限流
Semaphore sem=new Semaphore(3);
// 6個執行緒去搶佔
for (int i = 0; i < 6; i++) {
new Thread(()-> {
// acquire 得到
try {
sem.acquire();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
sem.release(); // 釋放
}
}).start();
}
}
}
sem.acquire(); 獲得,假設如果已經滿了,等待,直到被釋放為止
sem.release(); 釋放,會將當前的訊號量釋放+1,然後喚醒等待的執行緒
作用:多個共享資源互斥的使用!併發限流,控制最大的執行緒數
9.讀寫鎖
package com.kuang.demo01.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
// 獨佔鎖/共享鎖 排他鎖/共享鎖
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache= new MyCache();
// 寫入
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp);
}).start();
}
// 讀取
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
}).start();
}
}
}
class MyCache{
private volatile Map<String,Object>map=new HashMap<String,Object>();
// 讀寫鎖:更加細粒度的鎖
private ReadWriteLock lock=new ReentrantReadWriteLock();
// 存,寫
public void put(String key,Object 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) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
lock.writeLock().unlock();
}
}
// 取,讀
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"讀取");
Object o=map.get(key);
System.out.println(Thread.currentThread().getName()+"ok");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
}
10.阻塞佇列
方式 | 丟擲異常 | 不會丟擲異常,有返回值 | 阻塞 | 超時等待 |
---|---|---|---|---|
新增 | add | offer | put | offer(arg1,arg2,arg3) |
移除 | remove | poll | take | poll(arg1,arg2) |
判斷佇列首尾 | element | peek | - | - |
package com.kuang.demo01.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
test4();
}
// 丟擲異常
public static void test1(){
// 佇列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
// java.lang.IllegalStateException: Queue full 佇列已滿
// blockingQueue.add("d");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException 沒有元素
// System.out.println(blockingQueue.remove());
// 檢查隊首 java.util.NoSuchElementException
System.out.println(blockingQueue.element());
}
// 有返回值,沒有異常
public static void test2() {
// 佇列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// false 不丟擲異常
// blockingQueue.offer("d");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// null 取不到值
// System.out.println(blockingQueue.poll());
// 檢查隊首 null
System.out.println(blockingQueue.peek());
}
// 等待,阻塞(一直阻塞)
public static void test3() throws InterruptedException {
// 佇列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// 佇列沒有位置了
// blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// 沒有這個元素,一直阻塞
// System.out.println(blockingQueue.take());
}
// 等待,阻塞(等待超時)
public static void test4() throws InterruptedException {
// 佇列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// 佇列沒有位置了,等待超過兩秒退出
// blockingQueue.offer("d",2,TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 等待超過兩秒退出
// System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
}
SynchronousQueue 同步佇列
沒有容量,進去一個元素,必須等待取出來之後,才能再往裡面放一個元素
package com.kuang.demo01.bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步佇列
* 和其他的BlockingQueue 不一樣,SynchronousQueue 不儲存元素
* put 了一個元素,必須從裡面先take取出來,否則不能再put進去值
* 專案名稱:doncic
* 類名稱:SyschronousQueueDemo
* 類描述:
* 建立人:PC1
* 建立時間:2020年11月26日 上午10:31:01
* @version
*/
public class SyschronousQueueDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 同步佇列
BlockingQueue<String> blockingQueue=new SynchronousQueue<String>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"t2").start();
}
}
11.執行緒池(重點)
執行緒池:3大方法,7大引數,4種拒絕策略
池化技術
程式的執行,本質:佔用系統的資源!優化資源的使用!
執行緒池,連線池,記憶體池,物件池,常量池……(無需頻繁建立和銷燬,十分浪費資源)
池化技術:事先準備好一些資源,有人要用,就來我這裡拿,用完之後還給我
執行緒池的好處:
1.降低資源的消耗
2.提高響應的速度
3.方便管理
執行緒複用,可以控制最大併發數,管理執行緒
執行緒
三大方法
package com.kuang.demo01.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Executors 工具類,3大方法
// 使用了執行緒池之後,要使用執行緒池來建立執行緒
public class Demo01 {
public static void main(String[] args) {
// 單個執行緒
// ExecutorService threadPool=Executors.newSingleThreadExecutor();
// 固定執行緒池的大小
// ExecutorService threadPool= Executors.newFixedThreadPool(3);
// 可伸縮的
ExecutorService threadPool= Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 執行緒池用完,程式結束,關閉執行緒池
threadPool.shutdown();
}
}
}
七大引數
原始碼分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本質:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, // 核心執行緒池大小
int maximumPoolSize, // 最大的執行緒池大小
long keepAliveTime, // 存活時間
TimeUnit unit, // 時間單位
BlockingQueue<Runnable> workQueue, // 阻塞佇列
ThreadFactory threadFactory, // 執行緒工廠 建立執行緒的,一般不動
RejectedExecutionHandler handler) { // 拒絕策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
oom
記憶體洩露:申請使用完的記憶體沒有釋放,導致虛擬機器不能再次使用該記憶體,此時這段記憶體就洩露了,因為申請者不用了,而又不能被虛擬機器分配給別人用。
記憶體溢位:申請的記憶體超出了JVM能提供的記憶體大小,此時稱之為溢位。
手動建立執行緒池
package com.kuang.demo01.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// 四種拒絕策略
// new ThreadPoolExecutor.AbortPolicy() 不處理,直接丟擲異常
// new ThreadPoolExecutor.CallerRunsPolicy() 哪來的去哪裡
// new ThreadPoolExecutor.DiscardPolicy() 佇列滿了不會丟擲異常,會丟掉任務
// new ThreadPoolExecutor.DiscardOldestPolicy() 佇列滿了,嘗試去和最早的競爭,也不會丟擲異常
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool= new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
try {
// 最大承載:Deque + max
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 執行緒池用完,程式結束,關閉執行緒池
threadPool.shutdown();
}
}
}
最大執行緒到底該如何定義
-
CPU 密集型 ,幾核CPU就定義為幾,可以保持CPU效率最高
Runtime.getRuntime().availableProcessors();
獲取cpu核心數
-
IO 密集型,判斷程式種十分耗IO的執行緒,一般設定該IO的兩倍
12.四大函式式介面(必需掌握)
新時代程式設計師必須掌握:lambda表示式,鏈式程式設計,函式式介面,Stream流式計算
函式式介面:只有一個方法的介面
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 簡化程式設計模型,在新版本的框架底層大量應用@FunctionalInterface
// foreach(消費者類的函式式介面)
四大原生函式式介面
-
Consumer
-
Function
-
Predicate
-
Supplier
function
package com.kuang.demo01.function;
import java.util.function.Function;
// Function 函式型介面,有一個輸入引數,有一個輸出
// 只要是函式型介面,可以用lamdba表示式簡化
public class Demo01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 工具類:輸出輸入的值
/*Function<String,String> function=new Function<String,String>() {
@Override
public String apply(String str) {
// TODO Auto-generated method stub
return str;
}
};*/
// lambda 簡化
Function function=(Str)->{return Str;};
System.out.println(function.apply("asdf"));
}
}
predicate
package com.kuang.demo01.function;
import java.util.function.Predicate;
// 斷定型介面:有一個輸入引數,返回值只能是布林值
public class Demo02 {
public static void main(String[] args) {
// 判斷字串是否為空
/*Predicate<String> pre=new Predicate<String>() {
@Override
public boolean test(String t) {
// TODO Auto-generated method stub
return t.isEmpty();
}
};*/
Predicate<String> pre=t->t.isEmpty();
System.out.println(pre.test("123"));
}
}
consumer
package com.kuang.demo01.function;
import java.util.function.Consumer;
// consumer 消費型介面:只有輸入,沒有返回值
public class Demo03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*Consumer consumer=new Consumer<String>() {
@Override
public void accept(String t) {
// TODO Auto-generated method stub
System.out.println(t);
}
};*/
Consumer consumer=System.out::println;
consumer.accept("asdf");
}
}
Supplier
package com.kuang.demo01.function;
import java.util.function.Supplier;
// Supplier 供給型介面:沒有引數只有返回值
public class Demo04 {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*Supplier supplier=new Supplier<Integer>() {
@Override
public Integer get() {
// TODO Auto-generated method stub
return 1024;
}
};*/
Supplier supplier=()->1024;
System.out.println(supplier.get());
}
}
13.Stream流式計算
什麼是Stream流式計算
大資料基本就是儲存
+計算
集合,Mysql 本質就是儲存東西的
真正的計算都應該交給流來操作
package com.kuang.demo01.stream;
public class User {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public User() {
super();
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
package com.kuang.demo01.stream;
import java.util.Arrays;
import java.util.List;
/**
* 篩選
* 1.id必須是偶數
* 2.年齡必須大於23歲
* 3.使用者名稱轉為大寫字母
* 4.使用者名稱字字母倒著排序
* 5.只輸出一個使用者
*
* 專案名稱:doncic
* 類名稱:Test
* 類描述:
* 建立人:PC1
* 建立時間:2020年11月26日 下午3:30:43
* @version
*/
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
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(5,"e",25);
User u6=new User(6,"f",26);
// 集合就是儲存
List<User> list=Arrays.asList(u1,u2,u3,u4,u5,u6);
// 計算交給Stream流
// lambda表示式,鏈式程式設計,函式式介面,Stream流式計算
list.stream()
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
14.ForkJoin 分支合併
什麼是ForkJoin
ForkJoin在jdk1.7,並行執行任務!提高效率,大資料量
大資料:Map Reduce (把大任務拆分小任務)
ForkJoin 特點:工作竊取
這個裡面維護的都是雙端佇列
ForkJoin操作
package com.kuang.demo01.forkjoin;
import java.util.concurrent.RecursiveTask;
// 求和計算的任務
/*
* 如何使用forkjoin
* 1.forkjoinpool 通過它來執行
* 2.計算任務 forkjoinPool.execute(ForkJoinTask task)
* 3.計算類要繼承ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long>{
private Long start;
private Long end;
// 臨界值
private Long temp=1000L;
public ForkJoinDemo(Long start, Long end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// TODO Auto-generated method stub
if((end-start)<temp) {
Long sum=0L;
for (Long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}else {
// 分支合併計算 遞迴
long middle=(start+end)/2; // 中間值
ForkJoinDemo task1=new ForkJoinDemo(start,middle);
// 拆分任務,把任務壓入執行緒佇列
task1.fork();
ForkJoinDemo task2=new ForkJoinDemo(middle+1,end);
// 拆分任務,把任務壓入執行緒佇列
task2.fork();
return task1.join() + task2.join();
}
}
}
package com.kuang.demo01.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
test3();
}
//普通寫法
public static void test1() {
long start = System.currentTimeMillis();
Long sum=0L;
for(Long i=1L;i<=10_0000_0000;i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"時間:"+(end-start));
}
// ForkJoin
public static void test2() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
ForkJoinPool fork=new ForkJoinPool();
ForkJoinTask<Long> demo=new ForkJoinDemo(0L, 10_0000_0000L);
// 提交任務
ForkJoinTask<Long> submit=fork.submit(demo);
//fork.execute(demo);
Long sum=submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"時間:"+(end-start));
}
// stream
public static void test3() {
long start = System.currentTimeMillis();
Long sum=LongStream.rangeClosed(0, 10_0000_0000).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"時間:"+(end-start));
}
}
15.非同步回撥
Future 設計的初衷:對將來的某個事件的時間進行建模
package com.kuang.demo01.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
// 非同步呼叫Ajax
/*
* 非同步執行
* 成功回撥
* 失敗回撥
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// TODO Auto-generated method stub
// 沒有返回值的runAsync 非同步回撥
// 發起一個請求
/*CompletableFuture<Void> completeableFuture=CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
System.out.println(11);
// 獲取阻塞執行結果
completeableFuture.get();*/
// 有返回值的supplyAsync非同步回撥
// ajax,成功和失敗的回撥
CompletableFuture<Integer> completeableFuture=CompletableFuture.supplyAsync(()->{
int i=1/0;
return 1024;
});
System.out.println(completeableFuture.whenComplete((t,u)->{
System.out.println(t+" "+u);
}).exceptionally((t)->{
t.getMessage();
return 233;
}).get());
}
}
16.JMM
談談對Volatile的理解
Volatile 是java虛擬機器提供的輕量級的同步機制
1.保證可見性
2.不保證原子性
3.禁止指令重排
JMM java memory model
JMM: Java記憶體模型,不存在的概念
關於JMM的一些同步的約定:
1.執行緒解鎖前,必須把共享變數立刻
刷回主存
2.執行緒加鎖前,必須讀取主存中的最新值到工作記憶體中
3.加鎖和解鎖是同一把鎖
執行緒:工作記憶體,主記憶體
8種操作
記憶體互動操作有8種,虛擬機器實現必須保證每一個操作都是原子的,不可在分的(對於double和long型別的變數來說,load、store、read和write操作在某些平臺上允許例外)
-
- lock (鎖定):作用於主記憶體的變數,把一個變數標識為執行緒獨佔狀態
- unlock (解鎖):作用於主記憶體的變數,它把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他執行緒鎖定
- read (讀取):作用於主記憶體變數,它把一個變數的值從主記憶體傳輸到執行緒的工作記憶體中,以便隨後的load動作使用
- load (載入):作用於工作記憶體的變數,它把read操作從主存中變數放入工作記憶體中
- use (使用):作用於工作記憶體中的變數,它把工作記憶體中的變數傳輸給執行引擎,每當虛擬機器遇到一個需要使用到變數的值,就會使用到這個指令
- assign (賦值):作用於工作記憶體中的變數,它把一個從執行引擎中接受到的值放入工作記憶體的變數副本中
- store (儲存):作用於主記憶體中的變數,它把一個從工作記憶體中一個變數的值傳送到主記憶體中,以便後續的write使用
- write (寫入):作用於主記憶體中的變數,它把store操作從工作記憶體中得到的變數的值放入主記憶體的變數中
JMM對這八種指令的使用,制定瞭如下規則:
-
- 不允許read和load、store和write操作之一單獨出現。即使用了read必須load,使用了store必須write
- 不允許執行緒丟棄他最近的assign操作,即工作變數的資料改變了之後,必須告知主存
- 不允許一個執行緒將沒有assign的資料從工作記憶體同步回主記憶體
- 一個新的變數必須在主記憶體中誕生,不允許工作記憶體直接使用一個未被初始化的變數。就是懟變數實施use、store操作之前,必須經過assign和load操作
- 一個變數同一時間只有一個執行緒能對其進行lock。多次lock後,必須執行相同次數的unlock才能解鎖
- 如果對一個變數進行lock操作,會清空所有工作記憶體中此變數的值,在執行引擎使用這個變數前,必須重新load或assign操作初始化變數的值
- 如果一個變數沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他執行緒鎖住的變數
- 對一個變數進行unlock操作之前,必須把此變數同步回主記憶體
17.Volatile
保證可見性
package com.kuang.demo01.forkjoin.volatiles;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
// 不加volatile 程式死迴圈
private volatile static int num=0;
public static void main(String[] args) {
new Thread(()-> { // 執行緒1 對主記憶體的變化不知道的
while(num==0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
num =1;
System.out.print(num);
}
}
不保證原子性
原子性:ACID原則,不可分割
執行緒A在執行任務的時候,不能被打擾的,也不能被分割.要麼同時成功,要麼同時失敗
package com.kuang.demo01.forkjoin.volatiles;
public class VDemo02 {
// 不保證原子性
private volatile static int num=0;
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()-> {
for(int j=0;j<1000;j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
public static void add() {
num++;
}
}
如果不加lock和synchronized,怎麼樣保證原子性
package com.kuang.demo01.forkjoin.volatiles;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
// 不保證原子性
private volatile static AtomicInteger num=new AtomicInteger();
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()-> {
for(int j=0;j<1000;j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
public static void add() {
// 不是一個原子性操作
// num++;
num.getAndIncrement();
}
}
這些類的底層都直接和作業系統掛鉤,在記憶體中修改值,Unsafe是一個很特殊的存在
指令重排
什麼是指令重排:你寫的程式,計算機並不是按照你寫的那樣去執行的
原始碼-》編譯器優化的重排-》指令並行也可能會重排-》記憶體系統也會重排-》執行
處理器在進行指令重排的時候,會考慮資料之間的依賴性
int x=1; // 1
int y=2; // 2
x=x+5; // 3
y=x*x; // 4
我們所期望的:1234 但是可能執行的時候會變成 2134 1324
可不可能是:4123
可能造成影響的結果:a b x y這四個值預設都是0
執行緒a | 執行緒b |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的結果:x=0;y=0;但是可能由於指令重排
執行緒a | 執行緒b |
---|---|
b=1 | a=2 |
x=a | y=b |
異常的結果:x=2;y=1;
非計算機專業
volatile可以避免指令重排
記憶體屏障:cpu指令。
作用
- 保證特定的操作的執行順序
- 可以保證某些變數的記憶體可見性(利用這些特性volatile實現了可見性)
Volatile 是可以保證可見性,不能保證原子性,由於記憶體屏障,可以保證避免指令重排的現象產生
18.徹底玩轉單例模式
單例模式,顧名思義就是一個類只有一個例項,並且類負責建立自己的物件,這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。
為什麼使用單例模式而不使用靜態方法?
從面向物件的角度講:
雖然都能實現目的,但是他們一個是基於物件,一個是面向物件的,就像我們不面相物件也能解決問題一樣,面相物件的程式碼提供一個更好的程式設計思想。
如果一個方法和他所在類的例項物件無關,那麼它就應該是靜態的,反之他就應該是非靜態的。如果我們確實應該使用非靜態的方法,但是在建立類時又確實只需要維護一份例項時,就需要用單例模式了。
比如說我們在系統執行時候,就需要載入一些配置和屬性,這些配置和屬性是一定存在了,又是公共的,同時需要在整個生命週期中都存在,所以只需要一份就行,這個時候如果需要我再需要的時候new一個,再給他分配值,顯然是浪費記憶體並且再賦值沒什麼意義,所以這個時候我們就需要單例模式或靜態方法去維持一份且僅這一份拷貝,但此時這些配置和屬性又是通過面向物件的編碼方式得到的,我們就應該使用單例模式,或者不是面向物件的,但他本身的屬性應該是面對物件的,我們使用靜態方法雖然能同樣解決問題,但是最好的解決方案也應該是使用單例模式。
餓漢式
package com.kuang.demo01.single;
// 餓漢式單例
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 Hungry() {
}
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
DCL懶漢式
package com.kuang.demo01.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
// 懶漢式單例
public class LazyMan {
private static boolean foam=false;
private LazyMan() {
synchronized (LazyMan.class) {
if(foam==false) {
foam=true;
}else{
throw new RuntimeException("不要試圖用反射破壞異常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan LAZYMAN;
// 雙重檢測鎖模式的,DCL懶漢式
public static LazyMan getInstance() {
if(LAZYMAN==null) {
synchronized(LazyMan.class) {
if(LAZYMAN==null) {
// 不是一個原子性操作
LAZYMAN=new LazyMan();
/*
* 1.分配記憶體空間
* 2.執行構造方法,初始化物件
* 3.把這個物件指向這個空間
*
*/
}
}
}
return LAZYMAN;
}
// 反射
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
//LazyMan instance=LazyMan.getInstance();
Field foam=LazyMan.class.getDeclaredField("foam");
foam.setAccessible(true);
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1=declaredConstructor.newInstance();
LazyMan instance2=declaredConstructor.newInstance();
foam.set(instance1, false);
System.out.println(instance1);
System.out.println(instance2);
}
}
靜態內部類
package com.kuang.demo01.single;
// 靜態內部類
public class Holder {
private Holder() {
}
public static Holder getInstace() {
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
單例不安全,因為有反射
package com.kuang.demo01.single;
// enum 是一個什麼?本身也是一個Class類
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
列舉有兩個引數,string和int
列舉型別的最終反編譯原始碼是有參構造
19.深入理解CAS
什麼是CAS
package com.kuang.demo01.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet:比較並交換
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
// 期望,更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值達到了,就進行更新,否則就不進行更新,CAS 是CPU的併發原語
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
unsafe
CAS:比較當前工作記憶體中的值和主記憶體中的值,如果這個值是期望的,那麼則執行操作!如果不是就一直迴圈!
缺點:
1.迴圈會耗時
2.一次只能保證一個共享變數的原子性
3.ABA問題
CAS:ABA問題(狸貓換太子)
package com.kuang.demo01.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet:比較並交換
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
// 期望,更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值達到了,就進行更新,否則就不進行更新,CAS 是CPU的併發原語
// ==========================搗亂的執行緒=================================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// ==========================期望的執行緒=================================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
20.原子引用
解決aba問題,引入原子引用!對應的思想:樂觀鎖
帶版本號的原子操作
Integer 使用了物件快取機制,預設範圍是-128-127,推薦使用靜態工廠方法valueOf獲取物件例項,而不是new,因為valueOf使用快取,而new一定會建立新的物件分配新的記憶體空間
package com.kuang.demo01.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
// CAS compareAndSet:比較並交換
public static void main(String[] args) {
// Integer
// 正常業務操作,裡面比較的是一個物件
AtomicStampedReference<Integer> atomicInteger=new AtomicStampedReference<Integer>(1,1);
new Thread(()-> {
int stamp=atomicInteger.getStamp();// 獲得版本號
System.out.println("a1=>"+stamp);
System.out.println(atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp()+1));
System.out.println("a2=>"+atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp()+1));
System.out.println("a3=>"+atomicInteger.getStamp());
},"a").start();
new Thread(()-> {
int stamp=atomicInteger.getStamp();// 獲得版本號
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(1, 6, stamp, stamp+1));
System.out.println("b2=>"+atomicInteger.getStamp());
},"b").start();
}
}
21.各種鎖的理解
1.公平鎖,非公平鎖
公平鎖:非常公平,不能插隊,必須先來後到
非公平鎖:非常不公平,可以插隊(預設)
// 預設
public ReentrantLock() {
sync = new NonfairSync();
}
// 可選
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.可重入鎖
可重入鎖(遞迴鎖)
拿到了外面的鎖之後,就可以拿到裡面的鎖,自動獲得
package com.kuang.demo01.lock;
public class Demo01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Phone phone=new Phone();
new Thread(()-> {
phone.sms();
}).start();
new Thread(()-> {
phone.call();
}).start();
}
}
class Phone{
public synchronized void sms() {
System.out.println("sms");
call();// 這裡也有一把鎖
}
public synchronized void call() {
System.out.println("call");
}
}
3.自旋鎖
spinlock
package com.kuang.demo01.lock;
import java.util.concurrent.atomic.AtomicReference;
// 自旋鎖
public class spinLockDemo {
AtomicReference<Thread> atomic=new AtomicReference<>();
// 加鎖
public void myLock()
{
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===> mylock");
// 自旋鎖
while(!atomic.compareAndSet(null, thread)) {
}
}
// 解鎖
public void myUnLock()
{
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===> myUnLock");
// 自旋鎖
atomic.compareAndSet(thread, null);
}
}
package com.kuang.demo01.lock;
import java.util.concurrent.TimeUnit;
public class TestSpinLock {
public static void main(String[] args) {
// 底層使用的自旋鎖
spinLockDemo lock=new spinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"t2").start();
}
}
4.死鎖
死鎖是什麼
死鎖測試,怎麼排除死鎖:
1.互斥
2.佔有等待
3.迴圈等待
4.不可搶佔
解決問題
1.使用jps -l定位程序號
2.使用jstack 程序號檢視進行資訊
檢視
1.日誌
2.堆疊資訊