golang定義type為func 做一個回撥 - 解耦NewClient與Option的實現
阿新 • • 發佈:2022-05-10
1.java預設有兩個執行緒: 1.main執行緒 2.GC垃圾回收執行緒 2.java真的可以開啟執行緒麼? 答案是否定的,其實底層本地去呼叫是c++的方法,因為java是執行在虛擬機器上的,無法操作硬體! 原理如下: public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { //重點:執行緒內部呼叫的是start0的本地方法 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } //呼叫的其實是內部的start0方法 private native void start0(); 3.併發、並行 3.1 併發:多個執行緒操作同一個資源 CPU 一核情況下,模擬出來多條執行緒,快速交替,營造出共同進行的假象! 3.2 並行: CPU 多核情況下,多個執行緒可以同時執行! 程式獲取cpu核數: System.out.println("cpu核數:"+ Runtime.getRuntime().availableProcessors() ); 如何提高執行效率呢: 1.併發:CPU資源的充分利用 2.並行:執行緒池 多執行緒在多核cpu下的執行速率快於單執行緒,因為多核cpu下,多個執行緒是同時執行的, 但是單核cpu下,多執行緒的執行速率慢於單執行緒的速率,因為單核下,多執行緒是交替進行,會有上下文的切換! 結論: 1.單核CPU下,多執行緒不能實際的提高程式執行效率,只是為了能夠在不同的任務間切換,不同執行緒輪流使用CPU, 會涉及到下上文切換,會損失一部分的速率 2.多核cpu可以並行跑多個執行緒,但是是否能夠提升效率還得分情況: 2.1 有的任務:經過精心設計,將任務拆分,並行執行,當然可以提高程式得執行效率,但是不是所有得任務都能拆分 3.IO操作不佔用CPU,只是我門一半拷貝檔案使用的是【阻塞IO】,這時相當於執行緒雖然不用cpu,但需要一直等待IO結束,沒能充分利用執行緒 後面會有【非阻塞IO】和【非同步IO優化】
1.執行緒的狀態
總計6狀態: 1. NEW(新建) 2. RUNNABLE(執行) 3. BLOCKED(阻塞) 4. WAITING(等待) 5. TIMED_WAITING(超時等待,超時後就過) 6. TERMINATED(終止) 1.wait和sleep的區別 1.1 來自不同的類 wait-->Object sleep--->Thread 1.2 關於鎖的釋放 wait:會釋放鎖, sleep:不會釋放鎖 1.3 使用的範圍: wait--->只能在同步程式碼塊中使用 sleep--->可以在任意位置上使用 1.4 是否需要捕獲異常 wait:不需要捕獲異常 sleep:必須要捕獲異常
2.Lock鎖(重點)
傳統的synchronized 一個簡單的場景:售票 重點: 1.併發就是多個執行緒操作同一個資源,資源類儘量是一個純粹的類,不和其他做耦合,之前的是在資源類上實現runable介面,這樣不太好! 2.lambda表示式的使用: 示例如下: public class SellTicket { public static void main(String[] args) { /* 1.併發:多個執行緒操作同一個資源類,把資源丟入到執行緒中 2.lambda表示式:(請求引數)->{程式碼} */ Ticket ticket=new Ticket(); new Thread(()->{ for(int i =0;i<=50;i++){ ticket.sellTicket(); } },"A").start(); // 重點2:不使用lambda表示式,定義匿名內部類,並實現其run方法 //發現lambda表示式是省略了匿名內部類命和方法名 new Thread(new Runnable() { @Override public void run() { for(int i =0;i<=50;i++){ ticket.sellTicket(); } } }, "B").start(); new Thread(()->{ for(int i =0;i<=50;i++){ ticket.sellTicket(); } },"C").start(); } } //重點3:純粹的資源類,不和任何做耦合,不實現runable介面 class Ticket{ private int num=50; //重點4:方法上加鎖,可以按照預期賣票 public synchronized void sellTicket(){ if(num>0){ System.out.println(Thread.currentThread().getName()+"賣票!剩餘票:"+num--); } } }
上述例子使用synchronized實現了鎖,那如何根據Lock介面實現鎖呢?
由上述截圖可知,Lock介面有三個實現類:
1.ReentrantLock(普通鎖-->最常用)
2.ReentrantReadWriteLock.ReadLock(讀寫鎖-->讀鎖)
3.ReentrantReadWriteLock.WriteLock(讀寫鎖-->寫鎖)
具體的實現程式碼如下:
public class Juc_Test_Lock {
public static void main(String[] args) {
int num=50;
Ticket ticket=new Ticket(num);
new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"A").start();
new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"B").start();
new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"C").start();
}
}
@AllArgsConstructor
@NoArgsConstructor
class Ticket{
public Ticket(int ticketNum) {
this.ticketNum = ticketNum;
}
private int ticketNum;
//重點1:可重入鎖的使用
Lock lock=new ReentrantLock();
public void sell(){
//重點2:步驟1:加鎖
lock.lock();
try {
//步驟2:trycatch寫業務邏輯
if(ticketNum>0)
System.out.println(Thread.currentThread().getName()+"售票:剩餘-->"+ticketNum--);
} catch (Exception e) {
e.printStackTrace();
} finally {
//重點3:步驟3:解鎖
lock.unlock();
}
}
}
Lock lock=new ReentrantLock();程式碼細究:底層程式碼如下:
{
構造器1:發現建立的是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
構造器2:傳入引數,建立對應的鎖,預設是非公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
1.公平鎖FairSync:不能插隊,排隊進行
2.非公平鎖NonfairSync: 可以插隊,預設都是非公平鎖(包括synchronized )
重點:synchronized和lock鎖的區別
1.synchronized是內建關鍵字,Lock是java類
2.synchronized無法判斷獲取鎖的狀態,Lock可以判斷是否獲取鎖
3.synchronized會自動會釋放鎖,Lock必須要手動釋放鎖,要不會造成死鎖
4.synchronized(執行緒1獲取鎖,執行緒2等待,即使執行緒1阻塞);
Lock鎖就不一定會等待下去!
5.synchronized 可重入鎖,不可以中斷,非公平
Lock,可重入鎖,可以判斷鎖,非公平(可以自己設定)
6.synchronized:適合鎖少量的同步程式碼,
Lock適合鎖大量的同步程式碼
問題:鎖是什麼?如何判斷鎖的是誰?
1.物件(可能是多個)
2.Class(只能是一個)
生產者和消費者的synchronized 版本以及虛假喚醒問題
先看一個場景:
一個賣面的麵館,有一個做面的廚師和一個吃麵的食客,需要保證,廚師做一碗麵,食客吃一碗麵,
不能一次性多做幾碗面,更不能沒有面的時候吃麵;
按照上述操作,進行十輪做面吃麵的操作。
樣例程式碼:
public class Juc_test {
public static void main(String[] args){
Noodels noodels=new Noodels();
//重點1:建立兩個執行緒:1廚子造面 2.食客吃麵,先做10碗
new Thread(()->{
try {
for (int i=0;i<=10;i++){
noodels.makeNoodeles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"廚子").start();
new Thread(()->{
try {
for (int i=0;i<=10;i++){
noodels.eatNoodeles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"食客").start();
}
}
class Noodels{
//面的數量
private int num=0;
/*
做面方法
*/
public synchronized void makeNoo生產者和消費者的synchronized 版本以及虛假喚醒問題deles() throws InterruptedException {
if(num>0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
//面已做好,喚醒食客
this.notifyAll();
}
public synchronized void eatNoodeles() throws InterruptedException {
if (num==0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
this.notifyAll();
}
}
執行結果如下:很滿意,達到了效果,做一碗吃一碗的水平!
廚子造面了!當前面數:1
食客造面了!當前面數:0
廚子造面了!當前面數:1
食客造面了!當前面數:0
廚子造面了!當前面數:1
食客造面了!當前面數:0
廚子造面了!當前面數:1
問題來了:如果多個執行緒呢,即兩個廚子,兩個食客呢??改造程式碼,只需要在測試類裡多加幾個執行緒!
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"廚子A").start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"廚子B).start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客A").start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客B").start();
執行結果:打破了吃一碗造一碗的邏輯了,並且明明加了synchronized鎖,為什麼會這樣呢?這裡就涉及到了執行緒的虛假喚醒!
廚子A造面了!當前面數:1
食客A造面了!當前面數:0
廚子B造面了!當前面數:1
廚子A造面了!當前面數:2
廚子B造面了!當前面數:3
...
虛假喚醒
上面的問題就是"虛假喚醒"。 當我們只有一個廚師一個食客時,只能是廚師做面或者食客吃麵,並沒有其他情況; 但是當有兩個廚師,兩個食客時,就會出現下面的問題:1.初始狀態
2.廚師A得到操作權,發現面的數量為0,可以做面,面的份數+1,然後喚醒所有執行緒;
3.廚師B得到操作權,發現面的數量為1,不可以做面,執行wait操作;
4.廚師A得到操作權,發現面的數量為1,不可以做面,執行wait操作;
5.食客甲得到操作權,發現面的數量為1,可以吃麵,吃完麵後面的數量-1,並喚醒所有執行緒;
6.此時廚師A得到操作權了,因為是從剛才阻塞的地方繼續執行,就不用再判斷面的數量是否為0了,所以直接面的數量+1,並喚醒其他執行緒;
7.此時廚師B得到操作權了,因為是從剛才阻塞的地方繼續執行,就不用再判斷面的數量是否為0了,所以直接面的數量+1,並喚醒其他執行緒;
if(num != 0){
this.wait();
}
出現虛假喚醒的原因:
是從阻塞態到就緒態再到執行態沒有進行判斷(num的判斷),直接進行下述的+1或-1,
所以我們只需要讓其每次得到操作權時都進行判斷就可以了;
解決辦法:將if判斷改為while,每次都會判斷,喚醒以後都先判斷條件是否成立!
while(num != 0){
this.wait();
}
JUC版的生產者和消費者
使用Lock和Condition實現上述生產者和消費者問題:
樣例程式碼:
class Noodels{
//面的數量
private int num=0;
//重點1:獲取lock鎖(可重入鎖)
Lock lock = new ReentrantLock();
//重點2:獲取condition物件
Condition condition = lock.newCondition();
/*
做面方法
*/
public void makeNoodeles() throws InterruptedException {
//重點3:加鎖,不用synchronized關鍵字
lock.lock();
try {
while (num>0){
//重點4:使用condition.await來阻塞佇列
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
//重點5:condition.signalAll();來解鎖
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//重點5:解鎖
lock.unlock();
}
}
public void eatNoodeles() throws InterruptedException {
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"造面了!當前面數:"+num);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
問題:如何精確喚醒呢?例如A,B,C,D四個執行緒,如何精確的執行順序A->B->C->D
實現如下:
/**
* Bruk.liu
* A執行完呼叫B,B執行完呼叫C,C執行完呼叫A
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
//資源類
class Data3{
//建立Lock鎖
private Lock lock = new ReentrantLock();
//同步監視器,建立三個監視器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;//1就是A執行,2就是B執行
public void printA(){
lock.lock();
try {
while(number != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"===>AAAAAAAAA");
//喚醒指定執行緒
number = 2;
//呼叫指定監視器,喚醒指定執行緒
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number != 2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"===>BBBBBBBB");
//喚醒指定執行緒
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number != 3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"===>CCCCCCCCC");
//喚醒指定執行緒
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8鎖現象
1.synchronized鎖的物件是方法呼叫者!
2.static synchronized...鎖的是class這個類!
示例如下:
1.兩種方法都sendMessage/call都加上鎖synchronized,鎖的是方法的呼叫者!
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
//同一個資源物件Phone,所以所的是同一資源物件phone
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"發簡訊!").start();
//重點1:兩個執行緒間休眠4秒,能明顯看出哪個執行緒先執行
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打電話!").start();
}
}
class Phone{
//重點2:方法加鎖,並在列印前加入休眠,能明顯看出哪個先執行!
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("傳送簡訊");
}
public synchronized void call(){
System.out.println("打電話!");
}
}
輸出:因為兩個方法都加了synchronized,鎖的是方法的呼叫者,方法呼叫者是同一物件phone,所以鎖是同一把鎖!
傳送簡訊
打電話!
2.兩個物件:
建立兩個物件,因為synchronized鎖的是方法呼叫者,此處是 phone1和phone2,不是同一物件,所以執行緒2不會等待執行緒1執行完畢
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{phone1.sendMessage();},"發簡訊!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone2.call();},"打電話!").start();
結論:
打電話!
傳送簡訊
3.static synchronized:鎖定的是這個類模板
程式碼示例如下:
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"發簡訊!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打電話!").start();
}
}
class Phone{
重點1:方法上使用static synchronized修飾
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("傳送簡訊");
}
public static synchronized void call(){
System.out.println("打電話!");
}
}
輸出:static synchronized:鎖定的是這個類模板,執行緒2必須等待執行緒1執行完畢後才能執行!因為這是一把鎖
傳送簡訊
打電話!
4.上述情況下,方法加了static synchronized,但是是兩個物件
重點1:建立了兩個物件去執行static synchronized修飾的方法
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{phone1.sendMessage();},"發簡訊!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone2.call();},"打電話!").start();
輸出:發現執行緒2必須等執行緒1執行完畢後才執行,因為static synchronized鎖的是類模板,是一般鎖!
傳送簡訊
打電話!
5.staic synchronized和synchronized混用:
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
重點1:單個物件
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"發簡訊!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打電話!").start();
}
}
class Phone{
重點2:使用了static synchronized
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("傳送簡訊");
}
重點3:使用synchronized
public synchronized void call(){
System.out.println("打電話!");
}
}
輸出:因為staic synchronized和synchronized鎖的是不同的物件,synchronized鎖的是方法呼叫者phone物件,而staic synchronized鎖的是Phnoe類模板
所以是兩把不同的鎖,所以執行緒2不會等待1執行結束才執行!
打電話!
傳送簡訊