多線程與同步代碼塊詳解

線程是程序執(zhí)行的一條路徑, 一個(gè)進(jìn)程中可以包含多條線程。多線程并發(fā)執(zhí)行可以提高程序的效率,可以同時(shí)完成多項(xiàng)工作,多線程并發(fā)執(zhí)行的實(shí)質(zhì)就是CPU在做著高速的切換。多線程的應(yīng)用場(chǎng)景:紅蜘蛛同時(shí)共享屏幕給多個(gè)電腦;迅雷開啟多條線程一起下載;QQ同時(shí)和多個(gè)人一起視頻;服務(wù)器同時(shí)處理多個(gè)客戶端請(qǐng)求。

并行和并發(fā)的區(qū)別

  • 并行就是兩個(gè)任務(wù)同時(shí)運(yùn)行,就是甲任務(wù)進(jìn)行的同時(shí),乙任務(wù)也在進(jìn)行。(需要多核CPU)
  • 并發(fā)是指兩個(gè)任務(wù)都請(qǐng)求運(yùn)行,而處理器只能按受一個(gè)任務(wù),就把這兩個(gè)任務(wù)安排輪流進(jìn)行,由于時(shí)間間隔較短,使人感覺兩個(gè)任務(wù)都在運(yùn)行。
  • 比如我跟兩個(gè)網(wǎng)友聊天,左手操作一個(gè)電腦跟甲聊,同時(shí)右手用另一臺(tái)電腦跟乙聊天,這就叫并行。如果用一臺(tái)電腦我先給甲發(fā)個(gè)消息,然后立刻再給乙發(fā)消息,然后再跟甲聊,再跟乙聊。這就叫并發(fā)。

Java程序運(yùn)行原理

Java命令會(huì)啟動(dòng)java虛擬機(jī),啟動(dòng)JVM,等于啟動(dòng)了一個(gè)應(yīng)用程序,也就是啟動(dòng)了一個(gè)進(jìn)程。該進(jìn)程會(huì)自動(dòng)啟動(dòng)一個(gè) “主線程” ,然后主線程去調(diào)用某個(gè)類的 main 方法。JVM啟動(dòng)至少啟動(dòng)了垃圾回收線程和主線程,所以是多線程的。

代碼驗(yàn)證:

public class Demo1_Thread { //此程序的運(yùn)行結(jié)果是 "我是主線程的執(zhí)行代碼"和"垃圾被清掃了"交替執(zhí)行
    /**
    證明jvm是多線程的
     */
    public static void main(String[] args) {
        for(int i = 0; i < 100000; i++) {
            new Demo();
        }

        for(int i = 0; i < 10000; i++) {
            System.out.println("我是主線程的執(zhí)行代碼");
        }
    }
}

class Demo {
    @Override
    public void finalize() {
        System.out.println("垃圾被清掃了");
    }

}

多線程的實(shí)現(xiàn)

方式一:定義類繼承Thread,重寫run方法,把新線程要做的事寫在run方法中;創(chuàng)建線程對(duì)象,開啟(調(diào)用start())新線程, 內(nèi)部會(huì)自動(dòng)執(zhí)行run方法。

代碼演示:

public class Demo2_Thread {
    public static void main(String[] args) {
        MyThread mt = new MyThread();       //4,創(chuàng)建Thread類的子類對(duì)象
        mt.start();             //5,開啟線程
        MyThread mt1 = new MyThread();
        mt1.start();

        for(int i = 0; i < 1000; i++) {
            System.out.println("bb");
        }
    }

}
class MyThread extends Thread {     //1,繼承Thread
    public void run() {             //2,重寫run方法
        for(int i = 0; i < 1000; i++) {     //3,將要執(zhí)行的代碼寫在run方法中
            System.out.println("aaaaaaaaaaaa");
        }
    }
}

方式二:定義類實(shí)現(xiàn)Runnable接口,實(shí)現(xiàn)run方法,把新線程要做的事寫在run方法中,創(chuàng)建自定義的Runnable的子類對(duì)象,創(chuàng)建Thread對(duì)象,傳入Runnable,調(diào)用start()開啟新線程,內(nèi)部會(huì)自動(dòng)調(diào)用Runnable的run()方法。

代碼演示:

public class Demo3_Thread {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();   //4,創(chuàng)建Runnable的子類對(duì)象
        Thread t = new Thread(mr);      //5,將其當(dāng)作參數(shù)傳遞給Thread的構(gòu)造函數(shù)
        t.start();              //6,開啟線程
        for(int i = 0; i < 1000; i++) {
            System.out.println("bb");
        }
    }

}
class MyRunnable implements Runnable {          //1,定義一個(gè)類實(shí)現(xiàn)Runnable

    @Override
    public void run() {             //2,重寫run方法
        for(int i = 0; i < 1000; i++) {     //3,將要執(zhí)行的代碼寫在run方法中
            System.out.println("aaaaaaaaaaaa");
        }
    }

}

剖析實(shí)現(xiàn)Runnable接口的源碼

源碼解釋:構(gòu)造函數(shù)中傳入了Runnable的引用,成員變量記住了它,start()調(diào)用run()方法時(shí)內(nèi)部判斷成員變量Runnable的引用是否為空,不為空編譯時(shí)看的是Runnable的run(),運(yùn)行時(shí)執(zhí)行的是子類的run()方法。

源碼解析:

public class Thread implements Runnable {   //Thread類本身實(shí)現(xiàn)了Runnable接口
    /*省略代碼*/

    private Runnable target;    //一個(gè)Runnable類型的成員變量

    public Thread(Runnable target) {//Thread的有參構(gòu)造方法,傳入一個(gè)Runnable接口的子類對(duì)象target
        //調(diào)用init方法(下面這個(gè)) 把target傳入
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {    
        //init方法里面又調(diào)用另一個(gè)重載的init方法(下面這個(gè))把target傳入
        init(g, target, name, stackSize, null);     
    }
    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
        /*省略代碼*/
        this.target = target;//在init方法里面把成員變量target 進(jìn)行賦值
        /*省略代碼*/
    }
    public synchronized void start() {  //當(dāng)調(diào)用start方法的時(shí)候 內(nèi)部調(diào)用的是start0()方法
        /*省略代碼*/
        start0();
        /*省略代碼*/
    }

    private native void start0();   //start0()是native修飾的方法 底層是其他語(yǔ)言 我們無(wú)法查看,但是我們知道底層會(huì)調(diào)用run方法

    @Override
    public void run() {     //start0()的底層是會(huì)調(diào)用run()方法
        if (target != null) {   //判斷成員變量target是否為null
            target.run();   //如果target被賦值了,就要調(diào)用target的run方法
        }
    }
    /*省略代碼*/
}

多線程兩種實(shí)現(xiàn)方式的區(qū)別

繼承Thread : 由于子類重寫了Thread類的run(),當(dāng)調(diào)用start()時(shí), 直接找子類的run()方法;好處是:可以直接使用Thread類中的方法,代碼簡(jiǎn)單;弊端是:如果已經(jīng)有了父類,就不能用這種方法。

實(shí)現(xiàn)Runnable : 構(gòu)造函數(shù)中傳入了Runnable的引用, 成員變量記住了它, start()調(diào)用run()方法時(shí)內(nèi)部判斷成員變量Runnable的引用是否為空, 不為空編譯時(shí)看的是Runnable的run(),運(yùn)行時(shí)執(zhí)行的是子類的run()方法。好處是:即使自己定義的線程類有了父類也沒(méi)關(guān)系,因?yàn)橛辛烁割愐部梢詫?shí)現(xiàn)接口,而且接口是可以多實(shí)現(xiàn)的;弊端是:不能直接使用Thread中的方法需要先獲取到線程對(duì)象后,才能得到Thread的方法,代碼復(fù)雜。

匿名內(nèi)部類實(shí)現(xiàn)線程的兩種方式

public static void main(String[] args) {
    new Thread() {                              //1,繼承Thread類
        public void run() {                     //2,重寫run方法
            for(int i = 0; i < 1000; i++) {     //3,將要執(zhí)行的代碼寫在run方法中
                System.out.println("aaaaaaaaaaaaaa");
            }
        }
    }.start();                                  //4,開啟線程

    new Thread(new Runnable() {                 //1,將Runnable的子類對(duì)象傳遞給Thread的構(gòu)造方法
        public void run() {                     //2,重寫run方法
            for(int i = 0; i < 1000; i++) {     //3,將要執(zhí)行的代碼寫在run方法中
                System.out.println("bb");
            }
        }
    }).start();                                 //4,開啟線程
}

public final String getName():獲取線程對(duì)象的名稱。默認(rèn)情況下,名字的組成 Thread-編號(hào)(編號(hào)從0開始)
public final void setName(String name):設(shè)置線程名稱。

代碼演示:

//通過(guò)構(gòu)造方法給name賦值
public static void demo1() {
    new Thread("芙蓉姐姐") {        //通過(guò)構(gòu)造方法給name賦值
        public void run() {
            System.out.println(this.getName() + "....aaaaaaaaa");
        }
    }.start();
}
//通過(guò)setName()來(lái)設(shè)置線程的名字
public static void main(String[] args) {

    Thread t = new Thread() {
        public void run() {
            //this.setName("張三");       //在這兒設(shè)置也行  在外面設(shè)置名稱也行
            System.out.println(this.getName() + "....aaaaaaaaaaaaa");
        }
    };

    t.setName("張三");    //通過(guò)setName()來(lái)設(shè)置線程的名字
    t.start();
}

public static Thread currentThread():返回當(dāng)前正在執(zhí)行的線程對(duì)象引用

public static void sleep(long millis):讓當(dāng)前線程休眠millis毫秒

public final void setDaemon(boolean on):設(shè)置線程為守護(hù)線程,一旦前臺(tái)(主線程)結(jié)束,守護(hù)線程就結(jié)束了

public final void join():當(dāng)前線程暫停,等待指定的線程執(zhí)行結(jié)束后,當(dāng)前線程再繼續(xù)

public final void join(long millis):當(dāng)前線程暫停, 等待指定的線程執(zhí)行millis毫秒結(jié)束后, 當(dāng)前線程再繼續(xù)

public static void yield():暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。

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

public final void setPriority(int newPriority):更改線程的優(yōu)先級(jí)。線程默認(rèn)優(yōu)先級(jí)是5。范圍是1-10。

注意:優(yōu)先級(jí)可以在一定的程度上,讓線程獲較多的執(zhí)行機(jī)會(huì)。

代碼演示:

public static void main(String[] args) {
    final Thread t1 = new Thread() {
        public void run() {
            for(int i = 0; i < 10; i++) {
                System.out.println(getName() + "...aaaaaaaaaaaaa");
            }
        }
    };

    Thread t2 = new Thread() {
        public void run() {
            for(int i = 0; i < 10; i++) {
                if(i == 2) {
                    try {
                        t1.join();  //t1插隊(duì)后,t1執(zhí)行結(jié)束后 t2才能接著執(zhí)行
                        //t1.join(1);   //插隊(duì)指定的時(shí)間,過(guò)了指定時(shí)間后,兩條線程交替執(zhí)行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(getName() + "...bb");
            }
        }
    };
    t1.start();
    t2.start();
}

同步代碼塊

當(dāng)多線程并發(fā), 有多段代碼同時(shí)執(zhí)行時(shí), 我們希望某一段代碼執(zhí)行的過(guò)程中CPU不要切換到其他線程工作,這時(shí)就需要同步。如果兩段代碼是同步的,那么同一時(shí)間只能執(zhí)行一段, 在一段代碼沒(méi)執(zhí)行結(jié)束之前, 不會(huì)執(zhí)行另外一段代碼。所謂同步代碼塊就是使用synchronized關(guān)鍵字加上一個(gè)鎖對(duì)象來(lái)定義一段代碼,這就叫同步代碼塊,多個(gè)同步代碼塊如果使用相同的鎖對(duì)象,那么他們就是同步的。

代碼演示

public class Demo_Synchronized {
    public static void main(String[] args) {
        //匿名內(nèi)部類使用局部變量,局部變量前面必須加final修飾,為了延長(zhǎng)局部變量的聲明周期
        final Printer p = new Printer();    

        new Thread() {
            public void run() {
                while(true) {
                    p.print1(); //調(diào)用print1()
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    p.print2(); //調(diào)用print2()
                }
            }
        }.start();
    }

}

class Printer {
    Demo d = new Demo();
    public void print1() {  
        synchronized(d) {   //同步代碼塊,鎖機(jī)制,鎖對(duì)象可以是任意的
            //當(dāng)多線程并發(fā), 有多段代碼同時(shí)執(zhí)行時(shí)
            //我們希望下面代碼執(zhí)行的過(guò)程中CPU不要切換到其他線程工作
            System.out.print("黑");
            System.out.print("馬");
            System.out.print("程");
            System.out.print("序");
            System.out.print("員");
            System.out.print("\r\n");
        }
    }

    public void print2() {                      
        synchronized(d) {//如果說(shuō)兩塊代碼想同步的話,那么這兩個(gè)同步代碼塊的鎖對(duì)象必須是同一個(gè)鎖對(duì)象,所以說(shuō)上面的鎖對(duì)象是d,這一個(gè)鎖對(duì)象也是d
            //我們希望下面代碼執(zhí)行的過(guò)程中CPU不要切換到其他線程工作
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
}

class Demo{}

同步方法

使用synchronized關(guān)鍵字修飾一個(gè)方法,該方法中所有的代碼都是同步的,非靜態(tài)同步函數(shù)的鎖是當(dāng)前對(duì)象this,靜態(tài)的同步函數(shù)的鎖是當(dāng)前類的字節(jié)碼對(duì)象。

代碼演示:

public class Demo_Synchronized {
    public static void main(String[] args) {
        //匿名內(nèi)部類使用局部變量,局部變量前面必須加final修飾,為了延長(zhǎng)局部變量的聲明周期
        final Printer2 p = new Printer2();

        new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                }
            }
        }.start();
    }
}

class Printer2 {
    Demo d = new Demo();
    //非靜態(tài)的同步方法的鎖對(duì)象是神馬?
    //答:非靜態(tài)的同步方法的鎖對(duì)象是this
    //靜態(tài)的同步方法的鎖對(duì)象是什么?
    //是該類的字節(jié)碼對(duì)象
    public static synchronized void print1() {//同步方法只需要在方法上加synchronized關(guān)鍵字即可
        System.out.print("黑");
        System.out.print("馬");
        System.out.print("程");
        System.out.print("序");
        System.out.print("員");
        System.out.print("\r\n");
    }

    public static void print2() {
        synchronized(Printer2.class) {      
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
}

死鎖
同步代碼塊的嵌套就容易出現(xiàn)死鎖。所以開發(fā)中盡量避免同步代碼塊的嵌套。

代碼演示:

public class Demo5_DeadLock {
    /**
    @param args
    */
    private static String s1 = "筷子左";
    private static String s2 = "筷子右";

    public static void main(String[] args) {
        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s1) {
                        System.out.println(getName() + "...獲取" + s1 + "等待" + s2);
                        synchronized(s2) {
                            System.out.println(getName() + "...拿到" + s2 + "開吃");
                        }
                    }
                }
            }
        }.start();


        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s2) {
                        System.out.println(getName() + "...獲取" + s2 + "等待" + s1);
                        synchronized(s1) {
                            System.out.println(getName() + "...拿到" + s1 + "開吃");
                        }
                    }
                }
            }
        }.start();
    }
}

Vector,StringBuffer,Hashtable這些類之所以說(shuō)是同步的,是因?yàn)樗麄兝锩娴姆椒ㄉ隙技恿藄ynchronized

  • Collections.synchroinzedXxx(xxx):可以返回一個(gè)線程安全的集合
  • public static <T> Collection<T> synchronizedCollection(Collection<T> c)
  • public static <T> Set<T> synchronizedSet(Set<T> s)
  • public static <T> List<T> synchronizedList(List<T> list)
  • public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

注:Vector是線程安全的,ArrayList是線程不安全的

? StringBuffer是線程安全的,StringBuilder是線程不安全的

? Hashtable是線程安全的,HashMap是線程不安全的

想了解更多學(xué)習(xí)知識(shí),請(qǐng)關(guān)注微信公眾號(hào)“阿Q說(shuō)”,獲取更多學(xué)習(xí)資料吧!你也可以后臺(tái)留言說(shuō)出你的疑惑,阿Q將會(huì)在后期的文章中為你解答。每天學(xué)習(xí)一點(diǎn)點(diǎn),每天進(jìn)步一點(diǎn)點(diǎn)。

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    勝浩_ae28閱讀 5,257評(píng)論 0 23
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,108評(píng)論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽(yáng)閱讀 2,597評(píng)論 1 15
  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    小徐andorid閱讀 2,993評(píng)論 3 53
  • 有時(shí)候我會(huì)莫名其妙就負(fù)能量很強(qiáng),覺得自己像是,怎么說(shuō)呢,好聽點(diǎn)就是節(jié)奏比較慢,難聽點(diǎn)就是混吃等死。 有的時(shí)候我腦子...
    Abbyyyyyy666閱讀 357評(píng)論 1 0

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