java 多線程

1 - 線程

1.1 - 進(jìn)程

進(jìn)程就是正在運(yùn)行中的程序(進(jìn)程是駐留在內(nèi)存中的)

  • 是系統(tǒng)執(zhí)行資源分配和調(diào)度的獨(dú)立單位
  • 每一進(jìn)程都有屬于自己的存儲(chǔ)空間和系統(tǒng)資源
  • 注意:進(jìn)程A和進(jìn)程B的內(nèi)存獨(dú)立不共享箫章。

1.2 - 線程

線程就是進(jìn)程中的單個(gè)順序控制流硼一,也可以理解成是一條執(zhí)行路徑

  • 單線程:一個(gè)進(jìn)程中包含一個(gè)順序控制流(一條執(zhí)行路徑)

  • 多線程:一個(gè)進(jìn)程中包含多個(gè)順序控制流(多條執(zhí)行路徑)

  • 在java語言中:
    線程A和線程B椎麦,堆內(nèi)存和方法區(qū)內(nèi)存共享。
    但是棧內(nèi)存獨(dú)立,一個(gè)線程一個(gè)棧。

  • 假設(shè)啟動(dòng)10個(gè)線程,會(huì)有10個(gè)棧空間滔悉,每個(gè)棧和每個(gè)棧之間,互不干擾单绑,各自執(zhí)行各自的回官,這就是多線程并發(fā)。

  • java中之所以有多線程機(jī)制搂橙,目的就是為了提高程序的處理效率歉提。

  • 對(duì)于單核的CPU來說,不能夠做到真正的多線程并發(fā)区转,但是可以做到給人一種“多線程并發(fā)”的感覺苔巨。對(duì)于單核的CPU來說,在某一個(gè)時(shí)間點(diǎn)上實(shí)際上只能處理一件事情废离,但是由于CPU的處理速度極快侄泽,多個(gè)線程之間頻繁切換執(zhí)行,跟人來的感覺是多個(gè)事情同時(shí)在做蜻韭。

f86be0ff79da458eb3d852b27a8c119b.png

1.3 -java中多線程的實(shí)現(xiàn)原理

36a712dbdff642048611a26849294d1e.png

就緒狀態(tài):就緒狀態(tài)的線程又叫做可運(yùn)行狀態(tài)悼尾,表示當(dāng)前線程具有搶奪CPU時(shí)間片的權(quán)力(CPU時(shí)間片就是執(zhí)行權(quán))。當(dāng)一個(gè)線程搶奪到CPU時(shí)間片之后肖方,就開始執(zhí)行run方法闺魏,run方法的開始執(zhí)行標(biāo)志著線程進(jìn)入運(yùn)行狀態(tài)。

運(yùn)行狀態(tài):run方法的開始執(zhí)行標(biāo)志著這個(gè)線程進(jìn)入運(yùn)行狀態(tài)俯画,當(dāng)之前占有的CPU時(shí)間片用完之后析桥,會(huì)重新回到就緒狀態(tài)繼續(xù)搶奪CPU時(shí)間片,當(dāng)再次搶到CPU時(shí)間之后,會(huì)重新進(jìn)入run方法接著上一次的代碼繼續(xù)往下執(zhí)行泡仗。

阻塞狀態(tài):當(dāng)一個(gè)線程遇到阻塞事件埋虹,例如接收用戶鍵盤輸入,或者sleep方法等沮焕,此時(shí)線程會(huì)進(jìn)入阻塞狀態(tài)吨岭,阻塞狀態(tài)的線程會(huì)放棄之前占有的CPU時(shí)間片拉宗。之前的時(shí)間片沒了需要再次回到就緒狀態(tài)搶奪CPU時(shí)間片峦树。

鎖池:在這里找共享對(duì)象的對(duì)象鎖線程進(jìn)入鎖池找共享對(duì)象的對(duì)象鎖的時(shí)候,會(huì)釋放之前占有CPU時(shí)間片旦事,有可能找到了魁巩,有可能沒找到,沒找到則在鎖池中等待姐浮,如果找到了會(huì)進(jìn)入就緒狀態(tài)繼續(xù)搶奪CPU時(shí)間片谷遂。(這個(gè)進(jìn)入鎖池,可以理解為一種阻塞狀態(tài))

1.4 - 多線程的實(shí)現(xiàn)方式(一)

  • 繼承Thread類
    1卖鲤、自定義一個(gè)類MyThread類肾扰,用來繼承與Thread類
    2、在MyThread類中重寫run()方法
    3蛋逾、在測試類中創(chuàng)建MyThread類的對(duì)象
    4集晚、啟動(dòng)線程
/**
 * @author Mr.樂
 * @Description
 */
public class Demo01 {
    public static void main(String[] args) {
        //創(chuàng)建線程
        MyThread t01 = new MyThread();
        MyThread t02 = new MyThread();
        MyThread t03 = new MyThread("線程03");
 
        //開啟線程
//        t01.run();
//        t02.run();
//        t03.run();
        // 不會(huì)啟動(dòng)線程,不會(huì)分配新的分支棧区匣。(這種方式就是單線程偷拔。)
        // start()方法的作用是:啟動(dòng)一個(gè)分支線程,在JVM中開辟一個(gè)新的椏鞴常空間莲绰,這段代碼任務(wù)完成之后,瞬間就結(jié)束了姑丑。
        // 這段代碼的任務(wù)只是為了開啟一個(gè)新的椄蚯空間,只要新的椪ぐВ空間開出來震肮,start()方法就結(jié)束了。線程就啟動(dòng)成功了昌屉。
        // 啟動(dòng)成功的線程會(huì)自動(dòng)調(diào)用run方法钙蒙,并且run方法在分支棧的棧底部(壓棧)。
        // run方法在分支棧的棧底部间驮,main方法在主棧的棧底部躬厌。run和main是平級(jí)的。
        t01.start();
        t02.start();
        t03.start();
        //設(shè)置線程名(補(bǔ)救的設(shè)置線程名的方式)
        t01.setName("線程01");
        t02.setName("線程02");
        //設(shè)置主線程名稱
        Thread.currentThread().setName("主線程");
        for (int i = 0; i < 50; i++) {
            //Thread.currentThread() 獲取當(dāng)前正在執(zhí)行線程的對(duì)象
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
class MyThread extends Thread{
    public MyThread() {
    }
 
    public MyThread(String name) {
        super(name);
    }
 
    //run方法是每個(gè)線程運(yùn)行過程中都必須執(zhí)行的方法
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(this.getName() + ":" + i);
        }
    }
}

此處最重要的為start()方法。單純調(diào)用run()方法不會(huì)啟動(dòng)線程扛施,不會(huì)分配新的分支棧鸿捧。
start()方法的作用是:啟動(dòng)一個(gè)分支線程,在JVM中開辟一個(gè)新的椄碓空間匙奴,這段代碼任務(wù)完成之后,瞬間就結(jié)束了妄荔。線程就啟動(dòng)成功了泼菌。
啟動(dòng)成功的線程會(huì)自動(dòng)調(diào)用run方法(由JVM線程調(diào)度機(jī)制來運(yùn)作的),并且run方法在分支棧的棧底部(壓棧)啦租。
run方法在分支棧的棧底部哗伯,main方法在主棧的棧底部。run和main是平級(jí)的篷角。
單純使用run()方法是不能多線程并發(fā)的焊刹。

1.5 - 設(shè)置和獲取線程名

  • 設(shè)置線程名
    setName(String name): 設(shè)置線程名
    通過帶參構(gòu)造方法設(shè)置線程名

  • 獲取線程名
    getName():返回字符串形式的線程名
    Thread.CurrentThread(): 返回當(dāng)前正在執(zhí)行的線程對(duì)象

1.6 - 多線程的實(shí)現(xiàn)方式(二)

  • 實(shí)現(xiàn)Runnable接口
    1、自定義一個(gè)MyRunnable類來實(shí)現(xiàn)Runnable接口
    2恳蹲、在MyRunnable類中重寫run()方法
    3虐块、創(chuàng)建Thread對(duì)象,并把MyRunnable對(duì)象作為Tread類構(gòu)造方法的參數(shù)傳遞進(jìn)去
    4嘉蕾、啟動(dòng)線程
/**
 * @author Mr.樂
 * @Description
 */
public class Demo02 {
    public static void main(String[] args) {
        MyRunnable myRun = new MyRunnable();//將一個(gè)任務(wù)提取出來贺奠,讓多個(gè)線程共同去執(zhí)行
        //封裝線程對(duì)象
        Thread t01 = new Thread(myRun, "線程01");
        Thread t02 = new Thread(myRun, "線程02");
        Thread t03 = new Thread(myRun, "線程03");
        //開啟線程
        t01.start();
        t02.start();
        t03.start();
        //通過匿名內(nèi)部類的方式創(chuàng)建線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + " - " + i);
                }
            }
        },"線程04").start();
    }
}
//自定義線程類,實(shí)現(xiàn)Runnable接口
//這并不是一個(gè)線程類荆针,是一個(gè)可運(yùn)行的類敞嗡,它還不是一個(gè)線程。
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }
}

1.7 - 多線程的實(shí)現(xiàn)方式(三)

  • 實(shí)現(xiàn)Callable接口( java.util.concurrent.FutureTask; /JUC包下的航背,屬于java的并發(fā)包喉悴,老JDK中沒有這個(gè)包。新特性玖媚。)
    1箕肃、自定義一個(gè)MyCallable類來實(shí)現(xiàn)Callable接口
    2、在MyCallable類中重寫call()方法
    3今魔、創(chuàng)建FutureTask勺像,Thread對(duì)象,并把MyCallable對(duì)象作為FutureTask類構(gòu)造方法的參數(shù)傳遞進(jìn)去错森,把FutureTask對(duì)象傳遞給Thread對(duì)象吟宦。
    4、啟動(dòng)線程

      這種方式的優(yōu)點(diǎn):可以獲取到線程的執(zhí)行結(jié)果涩维。
      這種方式的缺點(diǎn):效率比較低殃姓,在獲取t線程執(zhí)行結(jié)果的時(shí)候,當(dāng)前線程受阻塞,效率較低蜗侈。
    
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * @author Mr.樂
 * @Description  線程實(shí)現(xiàn)的第三種方式
 */
public class Demo04 {
    public static void main(String[] args) throws Exception {
 
        // 第一步:創(chuàng)建一個(gè)“未來任務(wù)類”對(duì)象篷牌。
        // 參數(shù)非常重要,需要給一個(gè)Callable接口實(shí)現(xiàn)類對(duì)象踏幻。
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { // call()方法就相當(dāng)于run方法枷颊。只不過這個(gè)有返回值
                // 線程執(zhí)行一個(gè)任務(wù),執(zhí)行之后可能會(huì)有一個(gè)執(zhí)行結(jié)果
                // 模擬執(zhí)行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; //自動(dòng)裝箱(300結(jié)果變成Integer)
            }
        });
 
        // 創(chuàng)建線程對(duì)象
        Thread t = new Thread(task);
 
        // 啟動(dòng)線程
        t.start();
 
        // 這里是main方法该面,這是在主線程中夭苗。
        // 在主線程中,怎么獲取t線程的返回結(jié)果吆倦?
        // get()方法的執(zhí)行會(huì)導(dǎo)致“當(dāng)前線程阻塞”
        Object obj = task.get();
        System.out.println("線程執(zhí)行結(jié)果:" + obj);
        // main方法這里的程序要想執(zhí)行必須等待get()方法的結(jié)束
        // 而get()方法可能需要很久听诸。因?yàn)間et()方法是為了拿另一個(gè)線程的執(zhí)行結(jié)果
        // 另一個(gè)線程執(zhí)行是需要時(shí)間的。
        System.out.println("hello world!");
    }
}

1.8 -線程控制

方法名 說明
void yield() 使當(dāng)前線程讓步蚕泽,重新回到爭奪CPU執(zhí)行權(quán)的隊(duì)列中
static void sleep(long ms) 使當(dāng)前正在執(zhí)行的線程停留指定的毫秒數(shù)
void join() 等死(等待當(dāng)前線程銷毀后,再繼續(xù)執(zhí)行其它的線程)
void interrupt() 終止線程睡眠

1.8.1 -sleep()方法 (誰執(zhí)行誰就是當(dāng)前線程)

/**
 * @author Mr.樂
 * @Description 線程睡眠
 */
public class DemoSleep {
    public static void main(String[] args) {
        //        創(chuàng)建線程
        MyThread1 t01 = new MyThread1("黃固");
        MyThread1 t02 = new MyThread1("歐陽鋒");
        MyThread1 t03 = new MyThread1("段智興");
        MyThread1 t04 = new MyThread1("洪七公");
 
        //開啟線程
        t01.start();
        t02.start();
        t03.start();
        t04.start();
    }
}
class MyThread1 extends Thread{
    public MyThread1() {
    }
 
    public MyThread1(String name) {
        super(name);
    }
 
    @Override
    // 重點(diǎn):run()當(dāng)中的異常不能throws桥嗤,只能try catch
    // 因?yàn)閞un()方法在父類中沒有拋出任何異常须妻,子類不能比父類拋出更多的異常。
    public void run() {
        for (int i = 1; i < 50; i++) {
            System.out.println(this.getName() + "正在打出第 - " + i + "招");
 
            try {
                Thread.sleep(500);//讓當(dāng)前正在執(zhí)行的線程睡眠指定毫秒數(shù)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:run()方法中的異常只能try catch泛领,因?yàn)楦割悰]有拋出異常荒吏,子類不能拋出比父類更多的異常。

1.8.2 -interrupt()方法和stop()方法

/**
 * @author Mr.樂
 * @Description  終止線程
 */
public class DemoInterrupt {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 終斷t線程的睡眠(這種終斷睡眠的方式依靠了java的異常處理機(jī)制渊鞋。)
        t.interrupt();
//        t.stop(); //強(qiáng)行終止線程
        //缺點(diǎn):容易損壞數(shù)據(jù)  線程沒有保存的數(shù)據(jù)容易丟失
    }
}
class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---> begin");
        try {
            // 睡眠1年
            Thread.sleep(1000 * 60 * 60 * 24 * 365);
        } catch (InterruptedException e) {
//            e.printStackTrace();
        }
        //1年之后才會(huì)執(zhí)行這里
        System.out.println(Thread.currentThread().getName() + "---> end");
 
    }
}

1.8.3 -合理的終止線程

做一個(gè)boolean類型的標(biāo)記

/**
 * @author Mr.樂
 * @Description
 */
public class DemoSleep02 {
    public static void main(String[] args) {
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
 
        // 模擬5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 終止線程
        // 你想要什么時(shí)候終止t的執(zhí)行绰更,那么你把標(biāo)記修改為false,就結(jié)束了锡宋。
        r.run = false;
    }
}
class MyRunable4 implements Runnable {
 
    // 打一個(gè)布爾標(biāo)記
    boolean run = true;
 
    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            if(run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // return就結(jié)束了儡湾,你在結(jié)束之前還有什么沒保存的。
                // 在這里可以保存呀执俩。
                //save....
                //終止當(dāng)前線程
                return;
            }
        }
    }
}

1.8.4 - yield()

暫停當(dāng)前正在執(zhí)行的線程對(duì)象徐钠,并執(zhí)行其他線程
yield()方法不是阻塞方法。讓當(dāng)前線程讓位役首,讓給其它線程使用尝丐。
yield()方法的執(zhí)行會(huì)讓當(dāng)前線程從“運(yùn)行狀態(tài)”回到“就緒狀態(tài)”。
注意:在回到就緒之后衡奥,有可能還會(huì)再次搶到爹袁。

/**
 * @author Mr.樂
 * @Description 線程讓位
 */
public class DemoYield {
    public static void main(String[] args) {
        //創(chuàng)建線程
        MyThread5 t01 = new MyThread5("線程01");
        MyThread5 t02 = new MyThread5("線程02");
        MyThread5 t03 = new MyThread5("線程03");
 
        //開啟線程
        t01.start();
        t02.start();
        t03.start();
    }
}
class MyThread5 extends Thread{
    public MyThread5() {
    }
 
    public MyThread5(String name) {
        super(name);
    }
 
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            if(30 == i){
                Thread.yield();//當(dāng)循i環(huán)到30時(shí),讓線程讓步
                //1矮固、回到搶占隊(duì)列中失息,又爭奪到了執(zhí)行權(quán)
                //2、回到搶占隊(duì)列中,沒有爭奪到執(zhí)行權(quán)
            }
            System.out.println(this.getName() + ":" + i);
        }
    }
}

1.8.5 -join()

1.9 - 線程的調(diào)度

  • 線程調(diào)度模型
    • 均分式調(diào)度模型:所有的線程輪流使用CPU的使用權(quán)根时,平均分配給每一個(gè)線程占用CPU的時(shí)間瘦赫。
    • 搶占式調(diào)度模型:優(yōu)先讓優(yōu)先級(jí)高的線程使用CPU,如果線程的優(yōu)先級(jí)相同蛤迎,那么就會(huì)隨機(jī)選擇一個(gè)線程來執(zhí)行确虱,優(yōu)先級(jí)高的占用CPU時(shí)間相對(duì)來說會(huì)高一點(diǎn)點(diǎn)。

Java中JVM使用的就是搶占式調(diào)度模型

getPriority(): 獲取線程優(yōu)先級(jí)

setPriority: 設(shè)置線程優(yōu)先級(jí)

/**
 * @author Mr.樂
 * @Description  線程的調(diào)度
 */
public class Demo07 {
    public static void main(String[] args) {
        //創(chuàng)建線程
        MyThread t01 = new MyThread("線程01");
        MyThread t02 = new MyThread("線程02");
        MyThread t03 = new MyThread("線程03");
        //獲取線程優(yōu)先級(jí)替裆,默認(rèn)是5
//        System.out.println(t01.getPriority());
//        System.out.println(t02.getPriority());
//        System.out.println(t03.getPriority());
        //設(shè)置線程優(yōu)先級(jí)
        t01.setPriority(Thread.MIN_PRIORITY); //低  - 理論上來講校辩,最后完成
        t02.setPriority(Thread.NORM_PRIORITY); //中
        t03.setPriority(Thread.MAX_PRIORITY); //高  - 理論上來講,最先完成
        //開啟線程
        t01.start();
        t02.start();
        t03.start();
    }
}

2 - 線程的安全

2.1 - 數(shù)據(jù)安全問題

  • 是否具備多線程的環(huán)境
  • 是否有共享數(shù)據(jù)
  • 是否有多條語句操作共享數(shù)據(jù)
  • 例如:我和小明同時(shí)取一個(gè)賬戶的錢辆童,我取錢后數(shù)據(jù)還沒返回給服務(wù)器宜咒,小明又取了,這個(gè)時(shí)候小明的余額還是原來的把鉴。
  • 如何解決故黑?線程排隊(duì)執(zhí)行(不能并發(fā)),線程同步機(jī)制庭砍。

2.1.1 -變量對(duì)線程安全的影響

實(shí)例變量:在堆中场晶。
靜態(tài)變量:在方法區(qū)。
局部變量:在棧中怠缸。

以上三大變量中:
    局部變量永遠(yuǎn)都不會(huì)存在線程安全問題诗轻。
    因?yàn)榫植孔兞坎还蚕怼#ㄒ粋€(gè)線程一個(gè)棧揭北。)
    局部變量在棧中扳炬。所以局部變量永遠(yuǎn)都不會(huì)共享。

 實(shí)例變量在堆中搔体,堆只有1個(gè)恨樟。
靜態(tài)變量在方法區(qū)中,方法區(qū)只有1個(gè)嫉柴。
堆和方法區(qū)都是多線程共享的厌杜,所以可能存在線程安全問題。

局部變量+常量:不會(huì)有線程安全問題计螺。
成員變量:可能會(huì)有線程安全問題夯尽。

2.1.2 -模擬線程安全問題

public class Test {
    public static void main(String[] args) {
        // 創(chuàng)建賬戶對(duì)象(只創(chuàng)建1個(gè))
       Account act = new Account("act-001", 10000);
        // 創(chuàng)建兩個(gè)線程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        // 設(shè)置name
        t1.setName("t1");
        t2.setName("t2");
        // 啟動(dòng)線程取款
        t1.start();
        t2.start();
        //t1對(duì)act-001取款5000.0成功,余額5000.0
        //t2對(duì)act-001取款5000.0成功登馒,余額5000.0
    }
}
----------------------------------------------------
public class AccountThread extends Thread {
 
    // 兩個(gè)線程必須共享同一個(gè)賬戶對(duì)象匙握。
    private Account act;
 
    // 通過構(gòu)造方法傳遞過來賬戶對(duì)象
    public AccountThread(Account act) {
        this.act = act;
    }
 
    public void run(){
        // run方法的執(zhí)行表示取款操作。
        // 假設(shè)取款5000
        double money = 5000;
        // 取款
        // 多線程并發(fā)執(zhí)行這個(gè)方法陈轿。
        act.withdraw(money);
 
        System.out.println(Thread.currentThread().getName() + "對(duì)"+act.getActno()+"取款"+money+"成功圈纺,余額" + act.getBalance());
    }
}
------------------------------------------------
/**
 * @author Mr.樂
 * @Description
 */
public class Account {
        // 賬號(hào)
        private String actno;
        // 余額
        private double balance;
 
    public Account() {
        }
 
    public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
 
        public String getActno() {
            return actno;
        }
 
        public void setActno(String actno) {
            this.actno = actno;
        }
 
        public double getBalance() {
            return balance;
        }
 
        public void setBalance(double balance) {
            this.balance = balance;
        }
    //取款的方法
    public void withdraw(double money){
        // t1和t2并發(fā)這個(gè)方法秦忿。。蛾娶。灯谣。(t1和t2是兩個(gè)棧。兩個(gè)棧操作堆中同一個(gè)對(duì)象蛔琅。)
        // 取款之前的余額
        double before = this.getBalance(); // 10000
        // 取款之后的余額
        double after = before - money;
        // 在這里模擬一下網(wǎng)絡(luò)延遲胎许,100%會(huì)出現(xiàn)問題
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 更新余額
        // 思考:t1執(zhí)行到這里了,但還沒有來得及執(zhí)行這行代碼罗售,t2線程進(jìn)來withdraw方法了辜窑。此時(shí)一定出問題。
        this.setBalance(after);
    }
}

2.2 - 線程同步的利弊

  • 好處:解決了線程同步的數(shù)據(jù)安全問題

  • 弊端:當(dāng)線程很多的時(shí)候寨躁,每個(gè)線程都會(huì)去判斷同步上面的這個(gè)鎖穆碎,很耗費(fèi)資源,降低效率

2.3 -編程模型

異步編程模型:
線程t1和線程t2职恳,各自執(zhí)行各自的所禀,t1不管t2,t2不管t1话肖,
誰也不需要等誰北秽,這種編程模型叫做:異步編程模型。
其實(shí)就是:多線程并發(fā)(效率較高最筒。)

同步編程模型:
線程t1和線程t2,在線程t1執(zhí)行的時(shí)候蔚叨,必須等待t2線程執(zhí)行
結(jié)束床蜘,或者說在t2線程執(zhí)行的時(shí)候,必須等待t1線程執(zhí)行結(jié)束蔑水,
兩個(gè)線程之間發(fā)生了等待關(guān)系邢锯,這就是同步編程模型。
效率較低搀别。線程排隊(duì)執(zhí)行丹擎。

2.4 -線程同步

2.4.1 -線程同步方式

同步語句塊:synchronized(this){方法體} (synchronized括號(hào)后的數(shù)據(jù)必須是多線程共享的數(shù)據(jù),才能達(dá)到多線程排隊(duì))

//        以下代碼的執(zhí)行原理歇父?
//        1蒂培、假設(shè)t1和t2線程并發(fā),開始執(zhí)行以下代碼的時(shí)候榜苫,肯定有一個(gè)先一個(gè)后护戳。
//        2、假設(shè)t1先執(zhí)行了垂睬,遇到了synchronized媳荒,這個(gè)時(shí)候自動(dòng)找“后面共享對(duì)象”的對(duì)象鎖抗悍,
//        找到之后,并占有這把鎖钳枕,然后執(zhí)行同步代碼塊中的程序缴渊,在程序執(zhí)行過程中一直都是
//        占有這把鎖的。直到同步代碼塊代碼結(jié)束鱼炒,這把鎖才會(huì)釋放衔沼。
//        3、假設(shè)t1已經(jīng)占有這把鎖田柔,此時(shí)t2也遇到synchronized關(guān)鍵字俐巴,也會(huì)去占有后面
//        共享對(duì)象的這把鎖,結(jié)果這把鎖被t1占有硬爆,t2只能在同步代碼塊外面等待t1的結(jié)束欣舵,
//        直到t1把同步代碼塊執(zhí)行結(jié)束了,t1會(huì)歸還這把鎖缀磕,此時(shí)t2終于等到這把鎖缘圈,然后
//        t2占有這把鎖之后,進(jìn)入同步代碼塊執(zhí)行程序袜蚕。
//
//        這樣就達(dá)到了線程排隊(duì)執(zhí)行糟把。
//        這里需要注意的是:這個(gè)共享對(duì)象一定要選好了。這個(gè)共享對(duì)象一定是你需要排隊(duì)
//        執(zhí)行的這些線程對(duì)象所共享的牲剃。
        synchronized (this){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }

普通同步方法:修飾符 synchronized 返回值類型 方法名(形參列表){方法體}
synchronized出現(xiàn)在實(shí)例方法上遣疯,一定鎖的是this(此方法)。不能是其他的對(duì)象了凿傅。 所以這種方式不靈活缠犀。
另外還有一個(gè)缺點(diǎn):synchronized出現(xiàn)在實(shí)例方法上, 表示整個(gè)方法體都需要同步聪舒,可能會(huì)無故擴(kuò)大同步的 范圍辨液,導(dǎo)致程序的執(zhí)行效率降低。所以這種方式不常用箱残。

    public synchronized void withdraw(double money){
        double before = this.getBalance(); // 10000
        // 取款之后的余額
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 更新余額
        this.setBalance(after);
}

靜態(tài)同步方法:修飾符 synchronized static 返回值類型 方法名(形參列表){方法體}
靜態(tài)方法中不能使用this)表示找類鎖滔迈。類鎖永遠(yuǎn)只有1把。

2.5 -如何解決線程安全問題

是一上來就選擇線程同步嗎被辑?synchronized
不是燎悍,synchronized會(huì)讓程序的執(zhí)行效率降低,用戶體驗(yàn)不好敷待。
系統(tǒng)的用戶吞吐量降低间涵。用戶體驗(yàn)差。在不得已的情況下再選擇
線程同步機(jī)制榜揖。

第一種方案:盡量使用局部變量代替“實(shí)例變量和靜態(tài)變量”勾哩。

第二種方案:如果必須是實(shí)例變量抗蠢,那么可以考慮創(chuàng)建多個(gè)對(duì)象,這樣
實(shí)例變量的內(nèi)存就不共享了思劳。(一個(gè)線程對(duì)應(yīng)1個(gè)對(duì)象迅矛,100個(gè)線程對(duì)應(yīng)100個(gè)對(duì)象,
對(duì)象不共享潜叛,就沒有數(shù)據(jù)安全問題了秽褒。)

第三種方案:如果不能使用局部變量,對(duì)象也不能創(chuàng)建多個(gè)威兜,這個(gè)時(shí)候
就只能選擇synchronized了销斟。線程同步機(jī)制。

2.6 -Lock

應(yīng)用場景不同椒舵,不一定要在同一個(gè)方法中進(jìn)行解鎖蚂踊,如果在當(dāng)前的方法體內(nèi)部沒有滿足解鎖需求時(shí),可以將lock引用傳遞到下一個(gè)方法中笔宿,當(dāng)滿足解鎖需求時(shí)進(jìn)行解鎖操作犁钟,方法比較靈活。

   private Lock lock = new ReentrantLock();//定義Lock類型的鎖
   public  void withdraw(double money){
        // t1和t2并發(fā)這個(gè)方法泼橘。涝动。。炬灭。(t1和t2是兩個(gè)棧醋粟。兩個(gè)棧操作堆中同一個(gè)對(duì)象。)
        // 取款之前的余額
        lock.lock();//上鎖
        double before = this.getBalance(); // 10000
        // 取款之后的余額
        double after = before - money;
        // 在這里模擬一下網(wǎng)絡(luò)延遲重归,100%會(huì)出現(xiàn)問題
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 更新余額
        // 思考:t1執(zhí)行到這里了昔穴,但還沒有來得及執(zhí)行這行代碼,t2線程進(jìn)來withdraw方法了提前。此時(shí)一定出問題。
        this.setBalance(after);
        lock.unlock();//解鎖
    }

2.7 -死鎖

  • 形成原因
    當(dāng)兩個(gè)線程或者多個(gè)線程互相鎖定的情況就叫死鎖

  • 避免死鎖的原則
    順序上鎖泳唠,反向解鎖狈网,不要回頭

/**
 * @author Mr.樂
 * @Description 死鎖
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
 
        // t1和t2兩個(gè)線程共享o1,o2
        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);
 
        t1.start();
        t2.start();
    }
}
 
class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){
 
            }
        }
    }
}
 
class MyThread2 extends Thread {
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
 
            }
        }
    }
}

2.8 -守護(hù)線程

java語言中線程分為兩大類:
一類是:用戶線程
一類是:守護(hù)線程(后臺(tái)線程)
其中具有代表性的就是:垃圾回收線程(守護(hù)線程)。

  • 守護(hù)線程的特點(diǎn):
    一般守護(hù)線程是一個(gè)死循環(huán)笨腥,所有的用戶線程只要結(jié)束拓哺,
    守護(hù)線程自動(dòng)結(jié)束。

  • 注意:主線程main方法是一個(gè)用戶線程脖母。

  • 守護(hù)線程用在什么地方呢士鸥?
    每天00:00的時(shí)候系統(tǒng)數(shù)據(jù)自動(dòng)備份。
    這個(gè)需要使用到定時(shí)器谆级,并且我們可以將定時(shí)器設(shè)置為守護(hù)線程烤礁。
    一直在那里看著讼积,每到00:00的時(shí)候就備份一次。所有的用戶線程
    如果結(jié)束了脚仔,守護(hù)線程自動(dòng)退出勤众,沒有必要進(jìn)行數(shù)據(jù)備份了。

public class Demo09 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();
        t.setName("備份數(shù)據(jù)的線程");
 
        // 啟動(dòng)線程之前鲤脏,將線程設(shè)置為守護(hù)線程
        t.setDaemon(true);
        t.start();
        // 主線程:主線程是用戶線程
        for(int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 
class BakDataThread extends Thread {
    public void run(){
        int i = 0;
        // 即使是死循環(huán)们颜,但由于該線程是守護(hù)者,當(dāng)用戶線程結(jié)束猎醇,守護(hù)線程自動(dòng)終止窥突。
        while(true){
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3 -定時(shí)器

定時(shí)器的作用:
間隔特定的時(shí)間,執(zhí)行特定的程序硫嘶。

在java的類庫中已經(jīng)寫好了一個(gè)定時(shí)器:java.util.Timer阻问,可以直接拿來用。
不過音半,這種方式在目前的開發(fā)中也很少用则拷,因?yàn)楝F(xiàn)在有很多高級(jí)框架都是支持
定時(shí)任務(wù)的。

import java.util.Timer;
import java.util.TimerTask;
 
/**
 * @author Mr.樂
 * @Description 定時(shí)類
 */
public class DemoTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();//創(chuàng)建Timer定時(shí)器類的對(duì)象
 
        //匿名內(nèi)部類
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("我被執(zhí)行了曹鸠!~");
                System.gc();//告訴JVM運(yùn)行完畢煌茬,可以把我回收
            }
        },5000);
    }
}

3.1 -線程與定時(shí)器執(zhí)行軌跡不同

線程與定時(shí)器之間互不搶占CPU時(shí)間片

import java.util.Timer;
import java.util.TimerTask;
 
/**
 * @author Mr.樂
 * @Description 線程與定時(shí)器的執(zhí)行軌跡不同
 */
public class DemoTimer {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + "<--->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
 
        //定時(shí)器實(shí)現(xiàn)
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.gc();//將編程垃圾的定時(shí)器進(jìn)行回收
            }
        },5000);
    }
}

4 -生產(chǎn)者和消費(fèi)者

4.1 -關(guān)于Object類中的wait和notify方法。

第一:wait和notify方法不是線程對(duì)象的方法彻桃,是java中任何一個(gè)java對(duì)象都有的方法坛善,因?yàn)檫@兩個(gè)方式是Object類中自帶的。
wait方法和notify方法不是通過線程對(duì)象調(diào)用邻眷,
不是這樣的:t.wait()眠屎,也不是這樣的:t.notify()..不對(duì)。

第二:wait()方法作用:
Object o = new Object();
o.wait();
表示:
讓正在o對(duì)象上活動(dòng)的線程進(jìn)入等待狀態(tài)肆饶,無期限等待改衩,直到被喚醒為止。
o.wait();方法的調(diào)用驯镊,會(huì)讓“當(dāng)前線程(正在o對(duì)象上活動(dòng)的線程)”進(jìn)入等待狀態(tài)葫督。

第三:notify()方法作用:
Object o = new Object();
o.notify();
表示:喚醒正在o對(duì)象上等待的線程。還有一個(gè)notifyAll()方法:這個(gè)方法是喚醒o對(duì)象上處于等待的所有線程板惑。

注意:wait方法和notify方法需要建立在synchronized線程同步的基礎(chǔ)之上橄镜。
重點(diǎn):o.wait()方法會(huì)讓正在o對(duì)象上活動(dòng)的當(dāng)前線程進(jìn)入等待狀態(tài),并且釋放之前占有的o對(duì)象的鎖冯乘; o.notify()方法只會(huì)通知洽胶,不會(huì)釋放之前占有的o對(duì)象的鎖。

4.2 -生產(chǎn)者和消費(fèi)者模式

生產(chǎn)者與消費(fèi)者模式是并發(fā)裆馒、多線程編程中經(jīng)典的設(shè)計(jì)模式姊氓,通過wait和notifyAll方法實(shí)現(xiàn)丐怯。

例如:生產(chǎn)滿了,就不能繼續(xù)生產(chǎn)了他膳,必須讓消費(fèi)線程進(jìn)行消費(fèi)响逢。
消費(fèi)完了,就不能繼續(xù)消費(fèi)了棕孙,必須讓生產(chǎn)線程進(jìn)行生產(chǎn)舔亭。

而消費(fèi)和生產(chǎn)者共享的倉庫,就為多線程共享的了蟀俊,所以需要考慮倉庫的線程安全問題钦铺。

wait方法和notify方法建立在線程同步的基礎(chǔ)之上。因?yàn)槎嗑€程要同時(shí)操作一個(gè)倉庫肢预。有線程安全問題矛洞。

wait方法作用:o.wait()讓正在o對(duì)象上活動(dòng)的線程t進(jìn)入等待狀態(tài),并且釋放掉t線程之前占有的o對(duì)象的鎖烫映。

notify方法作用:o.notify()讓正在o對(duì)象上等待的線程喚醒沼本,只是通知,不會(huì)釋放o對(duì)象上之前占有的鎖锭沟。

例1:

/**
 * @author Mr.樂
 * @Description 生產(chǎn)者和消費(fèi)者模式
 */
public class wait_notify {
    public static void main(String[] args) {
        Box box = new Box();//實(shí)例化奶箱類
 
        Producer producer = new Producer(box);//生產(chǎn)者對(duì)象
        Customer customer = new Customer(box);//消費(fèi)者對(duì)象
 
        Thread tp = new Thread(producer);//創(chuàng)建生產(chǎn)者線程
        Thread tc = new Thread(customer);//創(chuàng)建消費(fèi)者線程
 
        //啟動(dòng)線程
        tp.start();
        tc.start();
    }
}
//奶箱類
class Box{
    private int milk;  //放入奶箱中的第幾瓶牛奶
    private boolean state = false; //默認(rèn)奶箱為空
 
    /**
     * 生產(chǎn)者生產(chǎn)(放)牛奶
     * @param milk  第幾瓶
     */
    public synchronized void put(int milk){
        if(state){  //true表示奶箱中有牛奶
            try {
                wait();  //等待抽兆,需要有人喚醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //沒有牛奶,需要生產(chǎn)牛奶
        this.milk = milk;
        System.out.println("王五將第" + this.milk + "瓶你牛奶放進(jìn)了奶箱中");
        this.state = true;//將奶箱狀態(tài)調(diào)整成有牛奶
        notifyAll();//喚醒全部正在等待的線程
    }
    /**
     * 消費(fèi)者取牛奶
     */
    public synchronized void get(){
        if(!state){  //true表示奶箱中有牛奶
            try {
                wait();  //等待族淮,需要有人喚醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有牛奶辫红,需要取牛奶
        System.out.println("張三將第" + this.milk + "瓶牛奶拿走補(bǔ)了身體!");
        this.state = false;//將奶箱狀態(tài)改變成空
        notifyAll();//喚醒全部正在等待的線程
    }
 
}
//生產(chǎn)者類
class Producer implements Runnable{
    private Box b;
 
    public Producer(Box b){
        this.b = b;
    }
 
    @Override
    public void run() {
        for (int i = 1; i < 8; i++) {
            b.put(i);//放牛奶祝辣,放幾瓶
        }
    }
}
//消費(fèi)者類
class Customer implements Runnable{
    private Box b;
 
    public Customer(Box b){
        this.b = b;
    }
 
    @Override
    public void run() {
        while (true){
            b.get();//消費(fèi)者取牛奶
        }
    }
}

例2:

import java.util.ArrayList;
import java.util.List;
 
/**
 * @author Mr.樂
 * @Description 生產(chǎn)者和消費(fèi)者模式02
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        // 創(chuàng)建1個(gè)倉庫對(duì)象贴妻,共享的。
        List list = new ArrayList();
        // 創(chuàng)建兩個(gè)線程對(duì)象
        // 生產(chǎn)者線程
        Thread t1 = new Thread(new Producer(list));
        // 消費(fèi)者線程
        Thread t2 = new Thread(new Consumer(list));
 
        t1.setName("生產(chǎn)者線程");
        t2.setName("消費(fèi)者線程");
 
        t1.start();
        t2.start();
    }
}
 
// 生產(chǎn)線程
class Producer implements Runnable {
    // 倉庫
    private List list;
 
    public Producer(List list) {
        this.list = list;
    }
    @Override
    public void run() {
        // 一直生產(chǎn)(使用死循環(huán)來模擬一直生產(chǎn))
        while(true){
            // 給倉庫對(duì)象list加鎖。
            synchronized (list){
                if(list.size() > 0){ // 大于0,說明倉庫中已經(jīng)有1個(gè)元素了碟摆。
                    try {
                        // 當(dāng)前線程進(jìn)入等待狀態(tài),并且釋放Producer之前占有的list集合的鎖绢片。
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能夠執(zhí)行到這里說明倉庫是空的,可以生產(chǎn)
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 喚醒消費(fèi)者進(jìn)行消費(fèi)
                list.notifyAll();
            }
        }
    }
}
 
// 消費(fèi)線程
class Consumer implements Runnable {
    // 倉庫
    private List list;
 
    public Consumer(List list) {
        this.list = list;
    }
 
    @Override
    public void run() {
        // 一直消費(fèi)
        while(true){
            synchronized (list) {
                if(list.size() == 0){
                    try {
                        // 倉庫已經(jīng)空了岛琼。
                        // 消費(fèi)者線程等待,釋放掉list集合的鎖
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能夠執(zhí)行到此處說明倉庫中有數(shù)據(jù)巢株,進(jìn)行消費(fèi)槐瑞。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 喚醒生產(chǎn)者生產(chǎn)。
                list.notifyAll();
            }
        }
    }
}

5 -線程池

5.1 - 概念

線程池就是首先創(chuàng)建一些線程阁苞,他們的集合稱之為線程池困檩。線程池在系統(tǒng)啟動(dòng)時(shí)會(huì)創(chuàng)建大量空閑線程祠挫,程序?qū)⒁粋€(gè)任務(wù)傳遞給線程池,線程池就會(huì)啟動(dòng)一條線程來執(zhí)行這個(gè)任務(wù)悼沿,執(zhí)行結(jié)束后線程不會(huì)銷毀(死亡)等舔,而是再次返回到線程池中成為空閑狀態(tài),等待執(zhí)行下一個(gè)任務(wù)糟趾。

5.2 - 線程池的工作機(jī)制

在線程池的編程模式下慌植,任務(wù)是分配給整個(gè)線程池的,而不是直接提交給某個(gè)線程义郑,線程池拿到任務(wù)后蝶柿,就會(huì)在內(nèi)部尋找是否有空閑的線程,如果有非驮,則將任務(wù)交個(gè)某個(gè)空閑線程交汤。

5.3 - 使用線程池的原因

多線程運(yùn)行時(shí),系統(tǒng)不斷創(chuàng)建和銷毀新的線程劫笙,成本非常高芙扎,會(huì)過度的消耗系統(tǒng)資源,從而可能導(dǎo)致系統(tǒng)資源崩潰填大,使用線程池就是最好的選擇戒洼。

5.4 - 可重用線程

方法名 說明
Executors.newCacheThreadPoll(); 創(chuàng)建一個(gè)可緩存的線程池
execute(Runnable run) 啟動(dòng)線程池中的線程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * @author Mr.樂
 * @Description  可重用線程池
 */
public class ExecutorsTest {
    public static void main(String[] args) {
        //創(chuàng)建線程池
        ExecutorService threadPoll = Executors.newCachedThreadPool();
 
        for (int i = 0; i < 10; i++) {
            //如果不睡眠,那么第一個(gè)執(zhí)行完的線程無法及時(shí)成為空閑線程栋盹,那么線程池就會(huì)讓一個(gè)新的線程執(zhí)行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //每次循環(huán)都會(huì)開啟一個(gè)線程
            threadPoll.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在被執(zhí)行施逾!~");
                }
            });
        }
        threadPoll.shutdown();//關(guān)閉線程池
        //線程池是無限大,當(dāng)執(zhí)行當(dāng)前任務(wù)時(shí)例获,上一個(gè)任務(wù)已經(jīng)完成汉额,會(huì)重復(fù)執(zhí)行上一個(gè)任務(wù)的線程,而不是每次使用新的線程
    }
}

6 -多線程并發(fā)的線程安全問題

了解了線程池榨汤,接下來從底層講一下多線程并發(fā)的安全問題蠕搜。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * @author Mr.樂
 * @Description  并發(fā)安全
 */
public class MyTest {
    //定義靜態(tài)變量
    static int a=0;
    static int count=2000;
    public static void main(String[] args) {
       //創(chuàng)建線程池
        ExecutorService service = Executors.newCachedThreadPool();
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    a++;
                }
            });
        }
        關(guān)閉線程池
        service.shutdown();
        System.out.println(a);
        //1987
    }
}

以上程序運(yùn)行并沒有達(dá)到預(yù)期的2000,此處多線程并發(fā)收壕,a共享妓灌,所以沒達(dá)到2000

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * @author Mr.樂
 * @Description  并發(fā)安全
 */
public class MyTest {
    static int a=0;
    static int count=2000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //閉鎖 在一些條件下可放開  參數(shù):加多少把鎖
        CountDownLatch countDownLatch=new CountDownLatch(count);
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    a++;
                    //解一把鎖
                    countDownLatch.countDown();
                }
            });
        }
        service.shutdown();
        //會(huì)進(jìn)入阻塞狀態(tài)  什么時(shí)候把鎖全解了   阻塞狀態(tài)才會(huì)解除
        countDownLatch.await();
        System.out.println(a);
        //1987
    }
}

此處所用的加鎖方法也沒有實(shí)現(xiàn)預(yù)期效果。

6.1 -CPU多級(jí)緩存

35d273bb3bb3449fa3d0302d3dffbccb.png

打開任務(wù)管理器蜜宪,在性能中可查看CPU的多級(jí)緩存虫埂。

程序進(jìn)程中的數(shù)據(jù),都在內(nèi)存中存著圃验。 而CPU緩存掉伏,是為了解決內(nèi)存沒有CPU快的問題。當(dāng)一個(gè)數(shù)據(jù)需要CPU修改,而內(nèi)存無法及時(shí)給CPU返回?cái)?shù)據(jù)斧散,就會(huì)拖慢CPU的運(yùn)行速度供常。所以有了CPU緩存。

當(dāng)CPU需要在內(nèi)存中讀數(shù)據(jù)時(shí)鸡捐,在時(shí)間局部性上(不久的將來)還得讀此數(shù)據(jù)栈暇。,將此數(shù)據(jù)放在CPU緩存中箍镜。

當(dāng)用到內(nèi)存中數(shù)據(jù)(例如 a)時(shí)源祈,而數(shù)據(jù)旁邊的數(shù)據(jù)(例:static int a=0; int b=0; 用a時(shí)b為旁邊的數(shù)據(jù))在空間局部性上,會(huì)用到相鄰的數(shù)據(jù)(例如 b)鹿寨,CPU也會(huì)讀到b新博,將b數(shù)據(jù)放在CPU緩存中。

0be4a90ea2d244c9b44e3c48c02d5b9b.png

當(dāng)CPU讀取數(shù)據(jù)時(shí)脚草,會(huì)讓CPU緩存同步內(nèi)存中的數(shù)據(jù)赫悄。然后CPU緩存中的數(shù)據(jù)再交給CPU去修改。當(dāng)CPU修改完后馏慨,會(huì)把修改的數(shù)據(jù)傳給CPU緩存(此時(shí)CPU不需要等待)埂淮,再由CPU緩存?zhèn)鹘o內(nèi)存 。

當(dāng)CPU 01將數(shù)據(jù)修改完后写隶,CPU緩存01還沒有將數(shù)據(jù)傳給內(nèi)存倔撞,CPU緩存02讀到了a,此時(shí)a的值為0慕趴。

以下為線程安全的兩種方式痪蝇。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * @author Mr.樂
 * @Description  并發(fā)安全 synchronized
 */
public class MyTest {
    static int a=0;
    static int count=2000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //閉鎖 在一些條件下可放開  參數(shù):加多少把鎖
        CountDownLatch countDownLatch=new CountDownLatch(count);
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (MyTest.class) {
                        a++;
                        //解一把鎖
                        countDownLatch.countDown();
                    }
                }
            });
        }
        service.shutdown();
        //會(huì)進(jìn)入阻塞狀態(tài)  什么時(shí)候把鎖全解了   阻塞狀態(tài)才會(huì)解除
        countDownLatch.await();
        System.out.println(a);
        //2000
    }
}
-------------------------------------------------------------------
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
 
/**
 * @author Mr.樂
 * @Description  并發(fā)安全 synchronized
 */
public class MyTest {
    static int a=0;
    static int count=2000;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        //閉鎖 在一些條件下可放開  參數(shù):加多少把鎖
        CountDownLatch countDownLatch=new CountDownLatch(count);
        //信號(hào)量
        Semaphore semaphore=new Semaphore(1);
        for(int i=0;i<count;i++){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    try {  //拿走一個(gè)信號(hào)
                        semaphore.acquire();
                        a++;
                        //解一把鎖
                        countDownLatch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //釋放信號(hào)
                        semaphore.release();
                    }
                }
            });
        }
        service.shutdown();
        //會(huì)進(jìn)入阻塞狀態(tài)  什么時(shí)候把鎖全解了   阻塞狀態(tài)才會(huì)解除
        countDownLatch.await();
        System.out.println(a);
        //2000
    }
}

原文鏈接:https://blog.csdn.net/zdl66/article/details/126297036

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市冕房,隨后出現(xiàn)的幾起案子躏啰,更是在濱河造成了極大的恐慌,老刑警劉巖耙册,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件给僵,死亡現(xiàn)場離奇詭異,居然都是意外死亡详拙,警方通過查閱死者的電腦和手機(jī)帝际,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饶辙,“玉大人蹲诀,你說我怎么就攤上這事∑浚” “怎么了侧甫?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵珊佣,是天一觀的道長。 經(jīng)常有香客問我披粟,道長,這世上最難降的妖魔是什么冷冗? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任守屉,我火速辦了婚禮,結(jié)果婚禮上蒿辙,老公的妹妹穿的比我還像新娘拇泛。我一直安慰自己,他們只是感情好思灌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布俺叭。 她就那樣靜靜地躺著,像睡著了一般泰偿。 火紅的嫁衣襯著肌膚如雪熄守。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天耗跛,我揣著相機(jī)與錄音裕照,去河邊找鬼。 笑死调塌,一個(gè)胖子當(dāng)著我的面吹牛晋南,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播羔砾,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼负间,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姜凄?” 一聲冷哼從身側(cè)響起政溃,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎檀葛,沒想到半個(gè)月后玩祟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屿聋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年空扎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片润讥。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡转锈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出楚殿,到底是詐尸還是另有隱情撮慨,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站砌溺,受9級(jí)特大地震影響影涉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜规伐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一蟹倾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猖闪,春花似錦鲜棠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吵护,卻和暖如春盒音,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背何址。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工里逆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人用爪。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓原押,卻偏偏與公主長得像,于是被迫代替她去往敵國和親偎血。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诸衔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 Google Play應(yīng)用市場對(duì)于應(yīng)用的targetSdkVersion有了更為嚴(yán)格的要求。從 2018 年...
    申國駿閱讀 63,935評(píng)論 14 98
  • 《來,我們說說孤獨(dú)》 1·他們都在寫孤獨(dú) 一個(gè)詩人 如果 不說說 內(nèi)心的孤獨(dú) 不將孤獨(dú) 寫進(jìn)詩里 是不是很掉價(jià)呢 ...
    聽太陽升起閱讀 4,370評(píng)論 1 7
  • 自幼貧民窟長大的女子帖渠,僥幸多念了兩本書谒亦,枉以為可以與人平起平坐】战迹可是人生從來都是接力賽份招,我們卻天真的當(dāng)成了百米沖刺...
    Leeanran閱讀 5,762評(píng)論 1 5
  • 云舒老師,姓甚名誰狞甚,男的女的锁摔,多大歲數(shù),這些我全然不知哼审。之所以要寫寫云舒老師谐腰,完全是因?yàn)樗麑懙奈恼略斜缫粋€(gè)巨大的磁...
    數(shù)豆者m閱讀 2,334評(píng)論 5 9
  • """1.個(gè)性化消息: 將用戶的姓名存到一個(gè)變量中,并向該用戶顯示一條消息十气。顯示的消息應(yīng)非常簡單励背,如“Hello ...
    她即我命閱讀 2,862評(píng)論 0 5