Java多線程(下篇)

一、死鎖

1.同步鎖 解決了線程安全問(wèn)題,但會(huì)造成性能低下,還可能會(huì)引發(fā)一個(gè)問(wèn)題(罕見(jiàn)):死鎖

2.例子

a.一個(gè)地痞子死后,來(lái)到了地獄,到了吃飯的時(shí)候,他發(fā)現(xiàn)飯桌上面的飯菜非常豐盛,然后好多人圍著一張大桌子,一起吃飯,飯菜都上齊了,非常美味,
他們非常餓,開(kāi)吃吧...但是只有兩支筷子,而且是兩個(gè)人同時(shí)各拿一支筷子...如果沒(méi)有人愿意共享自己的筷子,那么他們就只能餓著...看著...饞著...留著口水.煎熬著...

b.爸爸說(shuō):“給我成績(jī)單,就給你零花錢”,兒子說(shuō):“給我零花錢,就給你成績(jī)單”,如果兩人互不相讓...

3.多個(gè)線程之間彼此占用對(duì)方想使用的資源

4.要使用非常規(guī)的手段演示b

5.分析b案例

a.爸爸要成績(jī)單 和 兒子要零花錢 這兩個(gè)事情是同時(shí)發(fā)生的 多線程

b.爸爸是一個(gè)線程 兒子是一個(gè)線程

c.嵌套鎖 + 交叉鎖


public class Son extends Thread {

    public void run() {

        for (int i = 0; i < 3; i++) {
            synchronized (MyLock.lock02_reportCart) {
                System.out.println("son:兒子有成績(jī)單");
                try {
                    com.afinalstone.MyLock.lock02_reportCart.wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (MyLock.lock02_money) {
                    System.out.println("son:兒子想要爸爸的零花錢");
                }
            }
        }

    }

}

public class Father extends Thread {

    public void run() {
        for (int i=0; i<3; i++){
            synchronized (MyLock.lock02_money) {
                System.out.println("Father:爸爸有零花錢");
                try {
                    MyLock.lock02_money.wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (MyLock.lock02_reportCart) {
                System.out.println("Father:爸爸想要兒子的成績(jī)單");
            }
        }


    }
}


public class MyLock {
    public static Object lock02_money = new Object();
    public static Object lock02_reportCart = new Object();
}

public class TestFatherSon {

    public static void main(String[] args) {
        new Father().start();
        new Son().start();
    }

}


二、解決死鎖問(wèn)題

1.同步代碼塊,一旦某個(gè)線程執(zhí)行了這個(gè)同步代碼塊,該代碼塊會(huì)自動(dòng)上鎖,當(dāng)前線程把代碼塊里面所有的代碼全部執(zhí)行完,鎖會(huì)自動(dòng)解開(kāi)

2.解決思路:不能等著自然解鎖,而是有程序員主動(dòng)的、及時(shí)的釋放鎖(解鎖-開(kāi)鎖)

3.開(kāi)鎖的方法:wait() wait(long) 開(kāi)鎖 在Object類中

4.該方法的作用:

a.釋放鎖

b.讓當(dāng)前線程休息

5.sleep()方法也可以讓當(dāng)前線程休息,沒(méi)有釋放鎖的功能

6.任意對(duì)象都有一把鎖,也有解鎖的方法

三、notify方法

1.無(wú)參的wait()一旦執(zhí)行了,會(huì)造成當(dāng)前線程一直睡.....

2.notify() 喚醒某個(gè)被wait()的線程

3.自身無(wú)法喚醒自身

4.wait()和notify()必須使用的是同一把鎖

5.之前的同步代碼塊是解決線程安全問(wèn)題,還可以實(shí)現(xiàn)線程間的通信(打電話、發(fā)消息)

6.結(jié)合wait()和notify()并依靠同一把鎖,可以實(shí)現(xiàn)線程間的通信(可不是互相發(fā)消息)。
鎖相當(dāng)于中間人的作用,兩個(gè)線程必須用同一把鎖(認(rèn)識(shí)同一個(gè)中間人)


public class Thread1 extends Thread{
    public void run() {
        
        for (int i = 1; i <= 20; i++) {
            synchronized (MyLock.lock01_thread) {
                System.out.println(i);
                if(i%5==0)
                {
                    try {
                        MyLock.lock01_thread.notify();
                        MyLock.lock01_thread.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }       
        }
    }
}

public class Thread2 extends Thread{        //喚醒Thread1的線程
    public void run(){
        for(int j=0;j<10;j++){
            synchronized (MyLock.lock01_thread) {
                try {
                    MyLock.lock01_thread.notify();
                    System.out.println("喚醒");
                    MyLock.lock01_thread.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
        }
    }
}

public class TestWaitNotify {

    public static void main(String[] args) {
        Thread1 t1=new Thread1();
        t1.start();
        Thread2 t2=new Thread2();
        t2.start();
    }

}

四、sleep方法使用實(shí)例


public class ThreadSleep {
    public static void main(String args[]) {
        MyThread thread = new MyThread();
        thread.start();//調(diào)用start()方法啟動(dòng)新開(kāi)辟的線程
        try {
            /*Thread.sleep(10000);
            sleep()方法是在Thread類里面聲明的一個(gè)靜態(tài)方法,因此可以使用Thread.sleep()的格式進(jìn)行調(diào)用
            */
            /*MyThread.sleep(10000);
            MyThread類繼承了Thread類,自然也繼承了sleep()方法,所以也可以使用MyThread.sleep()的格式進(jìn)行調(diào)用
            */
            /*靜態(tài)方法的調(diào)用可以直接使用“類名.靜態(tài)方法名”
              或者“對(duì)象的引用.靜態(tài)方法名”的方式來(lái)調(diào)用*/
            MyThread.sleep(10000);
            System.out.println("主線程睡眠了10秒種后再次啟動(dòng)了");
            //在main()方法里面調(diào)用另外一個(gè)類的靜態(tài)方法時(shí),需要使用“靜態(tài)方法所在的類.靜態(tài)方法名”這種方式來(lái)調(diào)用
            /*
            所以這里是讓主線程睡眠10秒種
            在哪個(gè)線程里面調(diào)用了sleep()方法就讓哪個(gè)線程睡眠,所以現(xiàn)在是主線程睡眠了。
            */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //thread.interrupt();//使用interrupt()方法去結(jié)束掉一個(gè)線程的執(zhí)行并不是一個(gè)很好的做法
        thread.flag = false;//改變循環(huán)條件,結(jié)束死循環(huán)
        /**
         * 當(dāng)發(fā)生InterruptedException時(shí),直接把循環(huán)的條件設(shè)置為false即可退出死循環(huán),
         * 繼而結(jié)束掉子線程的執(zhí)行,這是一種比較好的結(jié)束子線程的做法
         */
        /**
         * 調(diào)用interrupt()方法把正在運(yùn)行的線程打斷
         相當(dāng)于是主線程一盆涼水潑上去把正在執(zhí)行分線程打斷了
         分線程被打斷之后就會(huì)拋InterruptedException異常,這樣就會(huì)執(zhí)行return語(yǔ)句返回,結(jié)束掉線程的執(zhí)行
         所以這里的分線程在執(zhí)行完10秒鐘之后就結(jié)束掉了線程的執(zhí)行
         */
    }
}

class MyThread extends Thread {
    boolean flag = true;// 定義一個(gè)標(biāo)記,用來(lái)控制循環(huán)的條件

    public void run() {
        /*
         * 注意:這里不能在run()方法的后面直接寫(xiě)throw Exception來(lái)拋異常, 
         * 因?yàn)楝F(xiàn)在是要重寫(xiě)從Thread類繼承而來(lái)的run()方法,重寫(xiě)方法不能拋出比被重寫(xiě)的方法的不同的異常。
         *  所以這里只能寫(xiě)try……catch()來(lái)捕獲異常
         */
        while (flag) {
            System.out.println("==========" + new Date().toLocaleString() + "===========");
            try {
                /*
                 * 靜態(tài)方法的調(diào)用格式一般為“類名.方法名”的格式去調(diào)用 在本類中聲明的靜態(tài)方法時(shí)調(diào)用時(shí)直接寫(xiě)靜態(tài)方法名即可。 當(dāng)然使用“類名.方法名”的格式去調(diào)用也是沒(méi)有錯(cuò)的
                 */
                // MyThread.sleep(1000);//使用“類名.方法名”的格式去調(diào)用屬于本類的靜態(tài)方法
                sleep(1000);//睡眠的時(shí)如果被打斷就會(huì)拋出InterruptedException異常
                // 這里是讓這個(gè)新開(kāi)辟的線程每隔一秒睡眠一次,然后睡眠一秒鐘后再次啟動(dòng)該線程
                // 這里在一個(gè)死循環(huán)里面每隔一秒啟動(dòng)一次線程,每個(gè)一秒打印出當(dāng)前的系統(tǒng)時(shí)間
            } catch (InterruptedException e) {
                /*
                 * 睡眠的時(shí)一盤(pán)冷水潑過(guò)來(lái)就有可能會(huì)打斷睡眠 
                 * 因此讓正在運(yùn)行線程被一些意外的原因中斷的時(shí)候有可能會(huì)拋被打擾中斷(InterruptedException)的異常
                 */
                return;
                // 線程被中斷后就返回,相當(dāng)于是結(jié)束線程
            }
        }
    }
}

五、join方法使用實(shí)例

public class TestThreadJoin {
    public static void main(String args[]) {
        MyThread_Join thread2 = new MyThread_Join("MyThread_Join");
        // 在創(chuàng)建一個(gè)新的線程對(duì)象的同時(shí)給這個(gè)線程對(duì)象命名為mythread
        thread2.start();// 啟動(dòng)線程
        try {
            thread2.join();// 調(diào)用join()方法合并線程,將子線程mythread合并到主線程里面
            // 合并線程后,程序的執(zhí)行的過(guò)程就相當(dāng)于是方法的調(diào)用的執(zhí)行過(guò)程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i <= 5; i++) {
            System.out.println("I am main Thread");
        }
    }
}

class MyThread_Join extends Thread {
    MyThread_Join(String s) {
        super(s);
        /*
         * 使用super關(guān)鍵字調(diào)用父類的構(gòu)造方法 
         * 父類Thread的其中一個(gè)構(gòu)造方法:“public Thread(String name)” 
         * 通過(guò)這樣的構(gòu)造方法可以給新開(kāi)辟的線程命名,便于管理線程
         */
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("I am a\t" + getName());
            // 使用父類Thread里面定義的
            //public final String getName(),Returns this thread's name.
            try {
                sleep(1000);// 讓子線程每執(zhí)行一次就睡眠1秒鐘
            } catch (InterruptedException e) {
                return;
            }
        }
    }
}

六、yield方法使用實(shí)例

public class TestThreadYield {
    public static void main(String args[]) {
        MyThread_Yield t1 = new MyThread_Yield("t1");
        /* 同時(shí)開(kāi)辟了兩條子線程t1和t2,t1和t2執(zhí)行的都是run()方法 */
        /* 這個(gè)程序的執(zhí)行過(guò)程中總共有3個(gè)線程在并行執(zhí)行,分別為子線程t1和t2以及主線程 */
        MyThread_Yield t2 = new MyThread_Yield("t2");
        t1.start();// 啟動(dòng)子線程t1
        t2.start();// 啟動(dòng)子線程t2
        for (int i = 0; i <= 5; i++) {
            System.out.println("I am main Thread");
        }
    }
}

class MyThread_Yield extends Thread {
    MyThread_Yield(String s) {
        super(s);
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(getName() + ":" + i);
            if (i % 2 == 0) {
                yield();// 當(dāng)執(zhí)行到i能被2整除時(shí)當(dāng)前執(zhí)行的線程就讓出來(lái)讓另一個(gè)在執(zhí)行run()方法的線程來(lái)優(yōu)先執(zhí)行
                /*
                 * 在程序的運(yùn)行的過(guò)程中可以看到,
                 * 線程t1執(zhí)行到(i%2==0)次時(shí)就會(huì)讓出線程讓t2線程來(lái)優(yōu)先執(zhí)行 
                 * 而線程t2執(zhí)行到(i%2==0)次時(shí)也會(huì)讓出線程給t1線程優(yōu)先執(zhí)行
                 */
            }
        }
    }
}

七、生產(chǎn)者消費(fèi)者模式

1.研究什么問(wèn)題?農(nóng)夫不停的摘水果放到框里,小孩不停的從框里拿水果吃

2.生產(chǎn)速度 跟 消費(fèi)速度

3.農(nóng)夫和小孩都需要依靠wait()和notify()調(diào)節(jié)進(jìn)度


public class Child extends Thread {

    public void run() {
        while (true) {
            synchronized (Factory.numOfFactory) {
                if(Factory.numOfFactory.size()==0)
                {
                    try {
                        Factory.numOfFactory.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Factory.numOfFactory.remove(0);
                Factory.numOfFactory.notify();
                System.out.println("小孩吃了一個(gè)水果,還有" + Factory.numOfFactory.size() + "個(gè)水果");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

public class FruitFactory extends Thread{
    
    public void run(){
        while (true) {
            synchronized (Factory.numOfFactory) {
                
                if(Factory.numOfFactory.size()>=10)
                {
                    try {
                        Factory.numOfFactory.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Factory.numOfFactory.add(1);
                Factory.numOfFactory.notify();
                System.out.println("水果場(chǎng)生產(chǎn)一個(gè)水果,現(xiàn)在有" + Factory.numOfFactory.size()
                        + "個(gè)水果");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

public class Factory {
    public static ArrayList<Integer> numOfFactory = new ArrayList<>();  //規(guī)定長(zhǎng)度不能超過(guò)30
}

public class TestChild_FruitFactory {

    public static void main(String[] args) throws Exception {
        new FruitFactory().start();
        new Child().start();
    }

}

八、單例設(shè)計(jì)模式

1.實(shí)例化 實(shí)例

2.單個(gè)實(shí)例 只能創(chuàng)建出來(lái)一個(gè)對(duì)象

3.步驟

a.限制構(gòu)造方法

b.把創(chuàng)建對(duì)象的代碼放到一個(gè)靜態(tài)的方法中并返回這個(gè)對(duì)象

c.增加一個(gè)靜態(tài)屬性,當(dāng)該屬性的值為null時(shí)才創(chuàng)建對(duì)象

4.懶漢式這種寫(xiě)法是否存在線程安全問(wèn)題?會(huì)存在

5.只需要把getInstance方法改為同步方法即可解決線程安全問(wèn)題

6.餓漢式?jīng)]有線程安全問(wèn)題,因此推薦使用

7.一般情況,懶漢式更常見(jiàn)

//餓漢式
public class Student_hunger {
    private static Student_hunger s=new Student_hunger();
    
    private Student_hunger() {
    }
    
    public static Student_hunger getInstance()
    {
        return s;
    }
}

//懶漢式
public class Student_lazybones {

    private static Student_lazybones s=null;
    
    private Student_lazybones() {
    }
    
    public synchronized static Student_lazybones getInstance()
    {
        if(s==null)
        {
            s=new Student_lazybones();
        }
        return s;
    }

}
public class ThreadStudent extends Thread{
    
    public void run(){
        
        for (int i = 0; i < 10; i++) {
            Student_lazybones s= Student_lazybones.getInstance();
            System.out.println(s.hashCode());
        }
        
    }
}

public class Main {

    public static void main(String[] args) {
        testLazyBones();
        testHunger();
        testThreadStudent();
    }

    private static void testLazyBones(){
        Student_lazybones s1= Student_lazybones.getInstance();
        System.out.println(s1.hashCode());

        Student_lazybones s2= Student_lazybones.getInstance();
        System.out.println(s2.hashCode());
    }

    private static void testHunger(){
        Student_hunger ss1= Student_hunger.getInstance();
        System.out.println(ss1.hashCode());

        Student_hunger ss2= Student_hunger.getInstance();
        System.out.println(ss2.hashCode());
    }

    private static void testThreadStudent(){
        for (int i = 0; i < 3; i++) {
            new ThreadStudent().start();
        }
    }
}

項(xiàng)目地址:傳送門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽(yáng)閱讀 2,597評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,108評(píng)論 1 18
  • 1.解決信號(hào)量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 1,008評(píng)論 0 1
  • 林炳文Evankaka原創(chuàng)作品。轉(zhuǎn)載自http://blog.csdn.net/evankaka 本文主要講了ja...
    ccq_inori閱讀 735評(píng)論 0 4
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,298評(píng)論 0 14

友情鏈接更多精彩內(nèi)容