JAVA多線程(基礎(chǔ)知識筆記)

一、線程基礎(chǔ)

1.1線程的實現(xiàn)方法

(1)繼承Thread 類
(2)實現(xiàn)Runnable接口:Thread t1=new Thread(new Runnable(){重寫run方法})。或者也可以Thread t1=new Thread(t2)
(3)實現(xiàn)Callable接口,使用futureTask 獲得返回值。
自定義線程類中的成員變量針對其他的線程可以分為共享和不共享,多線程之間的交互是很重要的技術(shù)點。共享的成員變量就會出現(xiàn)線程安全問題。

1.2線程的常用API

mian()方法是被主線程調(diào)用的,自定義線程的構(gòu)造方法也是父線程調(diào)用的,自定義的run()方法是由子線程調(diào)用的。

isAlive():判斷當前線程是否處于活動狀態(tài)。初始化(Thread t1=new Thread())之后是false,start之后是true.活動狀態(tài)就是線程已經(jīng)啟動,且運行沒有結(jié)束。

在線程的構(gòu)造方法里面獲取的當前線程(Thread.currentThread())是main主線程。在線程的run()方法里面不管是獲取的當前線程,還是this都是只得當前線程。

sleep():在指定的毫秒數(shù)內(nèi)讓當前執(zhí)行的線程等待。

getId():獲取當前線程的唯一標識

1.3停止線程

停止一個線程意味著在線程處理完成任務之前結(jié)束正在執(zhí)行的操作。

1、使用退出標志,使線程正常退出,也就是當run()方法執(zhí)行完后退出。

boolean flag=true; 在run()方法里面當true時執(zhí)行,在要結(jié)束時,設置false,退出線程。

2、stop()方法強制結(jié)束線程。

stop()方法已經(jīng)作廢,不建議使用。InterruptedException不會捕獲到使用stop()方法停止線程的異常,ThreadDeath 異??梢圆东@到。

強制讓線程停止可能使一些請理性工作得不到完成。另一個原因是使用stop()會對鎖定的對象解鎖,導致數(shù)據(jù)得不到同步處理,出現(xiàn)數(shù)據(jù)不一致性。

3、使用interrupt()方法中斷線程

使用interrupt()方法不會真正的結(jié)束線程,在當前線程中打上一個停止的標記,

Thread類提供了靜態(tài)interrupted()方法測試當前線程是否中斷,成員方法isinterrupted()方法測試線程是否中斷。

成員方法:interrupt():打上中斷標記;isinterrupted()判斷是否打上了中斷標記,如果打上了標記,會拋出interruptException異常。

靜態(tài)方法:Thread.interrupted(),返回當前的中斷標記,并清除。

t.isinterrupted()是檢查線程t是否被打上停止標記。Thread.interrupted()是檢查主線程是否被打上停止的標記。

當調(diào)用靜態(tài)方法Thread.interrupted()方法,在測試當前線程是否打上了中斷標志,同時還會清除這個狀態(tài)。如果連續(xù)兩次調(diào)用該方法,第二次就會返回false;

那怎么來停止線程呢?

要停止的時候用t.interrupt(),在run()方法里面判斷t.isinterrupted()。

如果線程中有sleep代碼,不管是否進入到sleep()狀態(tài),如果調(diào)用了interrupt()方法都會產(chǎn)生異常。

1.4暫停線程

成員方法:suspend()暫停。resume()繼續(xù)。這兩個方法也已經(jīng)廢棄了。因為當一個線程暫停了,將不能釋放占用的資源的鎖,會導致其他線程無法獲得這個資源的鎖,也會導致共享的數(shù)據(jù)不一致的現(xiàn)象。

1.5 yield方法

yield()讓線程放棄當前CPU資源,將資源讓給其他的任務去占用CPU時間,但是放棄的時間不確定,有可能剛放棄CPU資源,馬上又獲得了CPU資源。

1.6線程的優(yōu)先級

在操作系統(tǒng)中線程可以劃分優(yōu)先級,優(yōu)先級較高的線程會得到更多的CPU資源,也就讓CPU會優(yōu)先執(zhí)行優(yōu)先級較高的線程對象中的任務。設置線程優(yōu)先級有助于幫助線程調(diào)度器確定下一次選擇哪個線程優(yōu)先執(zhí)行。

使用setPriority()方法,優(yōu)先級1-10,如果設置小于1或大于10,JDK會拋出illegalArgumentException異常。JDK默認設置3個優(yōu)先級常量,MIN_PRIORITY=1,NORM_PRIORITY=5(默認),MAXPRIORITY=10

獲取線程的優(yōu)先級,可以用getPriority()方法。

線程的優(yōu)先級具有繼承性,在當前線程開啟另一個線程,子線程默認優(yōu)先級與父線程一樣。

高優(yōu)先級的線程總是大部分先執(zhí)行完,但不代表所有的都先執(zhí)行完。而且線程的優(yōu)先也還有隨機性,優(yōu)先級高的不一定每一次都先執(zhí)行完。

1.7守護線程

Java線程中有兩種線程:一種是用戶線程,一種是守護線程。

守護線程是一種特殊的線程,當進程中不存在用戶線程時,守護線程會自動銷毀。典型的守護進程的例子就是垃圾回收線程,當進程中沒有用戶線程,垃圾回收線程就沒有存在的必要,就會銷毀。

要先設置為守護進程t.setDaemon(true),再start().

二、線程的同步機制

java中的多線程中的同步,如何在java語言中開發(fā)出線程安全的程序,解決非線程安全帶來的問題。

2.1synchronized同步方法

1、局部變量永遠是線程安全的,因為是私有的。

2、成員變量不是線程安全的。在方法上加入synchronized關(guān)鍵字,將方法同步,可以實現(xiàn)成員變量的線程安全。

3、如果多個對象使用多個鎖。synchronize取得的鎖都是對象鎖,而不是把一段代碼或方法作為鎖。所以那個線程先執(zhí)行關(guān)鍵字修飾的方法,那個線程就持有該方法所屬對象的鎖,其他線程只能處于等待狀態(tài),前提是多個線程訪問的是同一個對象。如果多個線程訪問多個不同對象而不是同一個對象,JVM就會創(chuàng)建多個鎖。

4、synchronized方法鎖鎖定的是整個對象

例如A線程先持有對象的鎖,B線程就不可以異步方式調(diào)用對象使用synchronized修飾的方法,線程B只能等線程A的方法執(zhí)行完成釋放對象鎖才能執(zhí)行,也就是同步執(zhí)行。雖然是鎖住整個對象,但是B線程可以異步的調(diào)用對象里沒有使用synchronize關(guān)鍵字的方法。

5、鎖重入

關(guān)鍵詞synchronized擁有鎖重入的功能,也就是說在使用synchronized的時候,當一個線程得到一個對象鎖后再次請求這些對象鎖時是可以再次得到該對象鎖。就是在當前的synchronized方法里面可以進入其他的synchronized方法。

可重入鎖也支持父子類繼承的環(huán)境中。

6、鎖的自動釋放

當一個線程執(zhí)行的代碼出現(xiàn)異常,線程所持有的鎖都會釋放。其他線程就可以得到釋放的鎖。

7、同步不具有繼承性

父類的方法有synchronized關(guān)鍵字,子類繼承的方法沒有,這種情況子類的方法是不會被同步執(zhí)行的。

2.2synchronized同步語句塊

1、synchronized同步方法的缺點:所有的線程都要等待一個個執(zhí)行??赡茉谝粋€方法里面有的操作可能不涉及線程安全的問題,他們也會耗費一定的時間,這樣他們的執(zhí)行也要等。

2、synchronized同步代碼塊:

synchronized(this){需要同步的代碼塊} :括號里面設置鎖定的內(nèi)容,this代表鎖定當前的對象

3、怎么樣使用同步代碼塊解決

只在一部分代碼上同步。

4、半異步半同步

不在synchronized塊中的就是異步執(zhí)行,在synchronized塊中的代碼就是同步執(zhí)行的。

5、synchronized代碼塊間的同步性

在一個對象里使用多個synchronized(this)代碼塊時,當一個線程訪問對象的一個synchronized(this)同步代碼塊時,其他線程對同一對象的其他synchronized(this)同步代碼塊的訪問會被阻塞,說明synchronized使用的對象鎖是一個。

6、synchronized(this)代碼塊與synchronized同步方法一樣,也是鎖定當前對象。

7、使用任意對象作為對象鎖

除了可以使用synchronized(this)來同步代碼塊,java還支持任意對象作為對象鎖來實現(xiàn)同步功能。這個任意對象通常是成員變量或方法的參數(shù)。使用格式:synchronized(非this對象)

(1)在多個線程持有對象鎖為同一個對象的情況下,同一時間只有一個線程可以執(zhí)行synchronized(非this對象)同步代碼塊中的代碼。如果使用的不是同一個對象鎖,運行的結(jié)果就是異步調(diào)用,會交叉運行。

(2)非this對象具有的優(yōu)點:

1)如果一個類中有很多的synchronized方法,雖然可以同步,受到阻塞,所以影響效率。如果使用synchronized(非this對象)同步代碼塊,則synchronized(非this對象)代碼塊中的程序與同步方法是異步的,也不與其他的鎖(this)爭搶this鎖,大大提高運行效率。

2)synchronized(非this對象)還可以解決臟讀的問題,注意要是鎖定的同一個對象。

synchronized(非this對象 x)格式的寫法是將x對象本身作為對象鎖,這樣:

a、當多個線程同時執(zhí)行synchronized(x)同步代碼塊時呈同步效果。

b、當其他的線程執(zhí)行x對象中的synchronized同步方法時呈同步效果。

c、當其他線程執(zhí)行x對象方法里面的synchronized(this)代碼塊時也呈同步效果。

注意:如果其他線程調(diào)用不加synchronized關(guān)鍵字的方法時,還是異步調(diào)用。

8、synchronized同步靜態(tài)方法與synchronized(class)代碼塊

關(guān)鍵字synchronized還可以應用在靜態(tài)方法上,如果這樣就是對當前的*.java對于的Class類進行加鎖。從效果上看,與在synchronized非靜態(tài)方法效果一樣,但從本質(zhì)上講,synchronized靜態(tài)方法是給Class類上鎖,synchronized非靜態(tài)方法是對實例對象加鎖。他們的區(qū)別在于當Class鎖可以對類的所有對象實例起作用。

synchronized(class)同步代碼塊的作用和synchronized靜態(tài)方法的作用是一樣的

9、synchronized(String)同步代碼塊,如果其他類型的比如synchronized(非this對象)中間的對象都是同樣的字符串(常量池),那么就是一個對象,所以這兩個方法也會同步。所以如果我們想要避免一些不必要的同步,可以不用String,或者用new Object()重新new對象

10、怎么用synchronized同步代碼塊解決synchronized同步方法的無限等待的情況?

public class TestSynchronized1 {
    public static void main(String[] args) {
        Service2 service2=new Service2();
        Thread t1=new ThreadA(service2);
        t1.start();
        Thread t2=new ThreadB(service2);
        t2.start();
    }
}
class Service2{
    private Object lock1=new Object();
    private Object lock2=new Object();
     public void foo1(){
        synchronized (lock1){
        System.out.println("foo1方法開始執(zhí)行。。。");
        boolean iscontinue=true;
        while (iscontinue){

        }
        System.out.println("foo1方法執(zhí)行結(jié)束。。。");
        }
    }
    public void foo2(){
         synchronized (lock2) {
             System.out.println("foo2方法開始執(zhí)行。。。");
             System.out.println("foo2方法執(zhí)行結(jié)束。。。");
         }
    }
}
class ThreadA extends Thread{
    private Service2 service2;
    public ThreadA(Service2 service2){
        this.service2=service2;
    }

    @Override
    public void run() {
        service2.foo1();
    }
}
class ThreadB extends Thread{
    private Service2 service2;
    public ThreadB(Service2 service2){
        this.service2=service2;
    }

    @Override
    public void run() {
        service2.foo2();
    }
}

上面的代碼中foo1()和foo2()方法分別使用不同的對象鎖,可以避免foo1()方法死循環(huán)的時候其他的線程無法執(zhí)行foo2()方法。

11、死鎖

所謂死鎖是指多個線程在運行過程中因爭奪資源而造成的一種僵局,當線程處于這種僵持的狀態(tài)時如果沒有外力作用,這些線程都無法繼續(xù)進行。

public class TestSynchronized3 {
    public static void main(String[] args) throws InterruptedException {
        ThreadA1 t=new ThreadA1();
        t.setFlag("a");
        Thread t1=new Thread(t);
        t1.start();
        Thread.sleep(10);
        t.setFlag("b");
        Thread t2=new Thread(t);
        t2.start();
    }
}
class ThreadA1 extends Thread{
    private String flag;//標志,控制代碼以什么樣的方式運行
    private Object lock1=new Object();
    private Object lock2=new Object();

    public void setFlag(String flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            if ("a".equals(flag)) {
                synchronized (lock1) {
                    System.out.println("flag=" + flag);
                    Thread.sleep(3000);
                    synchronized (lock2) {
                        System.out.println("按lock1=>lock2的順序執(zhí)行");
                    }
                }
            } else {
                synchronized (lock2){
                    System.out.println("flag=" + flag);
                    Thread.sleep(3000);
                    synchronized (lock1) {
                        System.out.println("按lock2=>lock1的順序執(zhí)行");
                    }
                }

            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

死鎖產(chǎn)生的原因:
1)系統(tǒng)資源的競爭:系統(tǒng)資源的競爭會導致系統(tǒng)資源不足,以及資源分配不當,導致死鎖。
2)線程運行的順序不合適:線程在運行多個過程中請求和釋放資源的順序不當,會導致死鎖。
產(chǎn)生死鎖的必要條件:
1)互斥條件:線程要求對所分配的資源進行排他性控制,即在某個時間內(nèi)資源僅為一個線程所用。
2)請求和操持條件:當線程因請求資源而阻塞時,對已獲得的資源不釋放。

3)不剝奪條件:線程在已獲得的資源未使用之前,不能剝奪,只能在使用完時自己釋放。

4、環(huán)路等待條件:在發(fā)生死鎖時,必然存在一個線程等待另一個線程的環(huán)形鏈(lock1=>lock2=>lock1)

12、鎖對象的內(nèi)容改變

在synchronized(非this對象)同步的代碼塊里面,對這個非this的鎖對象進行改變,這樣在執(zhí)行完改變之后,鎖的對象發(fā)生的改變,其他的線程擁有這個鎖對象的線程可以進入該同步代碼塊。當然要讓第一個線程來的及改變鎖對象。例如String s="123" 改成了“456”。因為String都是在常量池里,改變了字面量,引用的對象也變了。

注意:只要對象不變,即使對象的屬性被改變了,運行的結(jié)果還是同步的。

2.3volatile關(guān)鍵字

volatile關(guān)鍵字是使變量可以在多個線程之間可見。

public class TestVolatile {
    public static void main(String[] args) {
        Service4 service4=new Service4();
        service4.foo();
        System.out.println(Thread.currentThread().getName()+"準備停止foo方法的循環(huán)");
        service4.flag=false;
    }


}
class Service4{
    public boolean flag=true;
    public void foo(){
        System.out.println("foo開始運行。。。");
        while (flag){

        }
        System.out.println("foo運行結(jié)束。。。");
    }
}

程序開始后并不能用flag停止下來,因為主線程一直在while循環(huán)里面。

public class TestVolatile {
    public static void main(String[] args) throws InterruptedException {
        Service4 service4=new Service4();
//        service4.foo();
        service4.start();
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"準備停止foo方法的循環(huán)");
        service4.flag=false;
    }


}
class Service4 extends Thread{
    public boolean flag=true;
    public void foo(){
        System.out.println("foo開始運行。。。");
        while (flag){

        }
        System.out.println("foo運行結(jié)束。。。");
    }

    @Override
    public void run() {
        foo();
    }
}

這種方法是也是不能夠停止的。這是因為JDK8以上版本默認用的是服務端模式,會對虛擬機優(yōu)化。
在啟動線程時,變量public boolean flag=true存儲在公共堆棧及線程的私有堆棧中。當使用服務器模式時為了線程運行的效率,線程一直在私有堆棧中取得flag的值一直都是true.當service設置flag時,設置的是公共堆棧中的flag,所以一直都是死循環(huán)狀態(tài),當使用volatile修飾成員變量后,會強制線程從公共堆棧中獲取變量的值。
synchronized和volatile的區(qū)別:
(1)volatile是線程同步的輕量級實現(xiàn),他的性能比synchronized好,并且volatile只能修飾變量。而synchronized可以修飾方法及代碼塊。隨著jdk的版本更新,synchronized在執(zhí)行效率也得到了提升,在開發(fā)中synchronized的使用率還是較高。
(2)多線程訪問volatile不會發(fā)生阻塞,而synchronized會發(fā)生阻塞。
(3)volatile能保證數(shù)據(jù)的可見性,不能保證原子性,而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內(nèi)存和公共內(nèi)存的數(shù)據(jù)做同步。
(4)volatile解決的是變量在多個線程之間的可見性,而synchronized是解決多個線程之間訪問資源的同步性。

三、線程間的通信

線程是程序中獨立的個體,但這些個體如果不經(jīng)過處理就能成為一個整體。線程間的通信就是成為整體的必用方案之一,可以說使線程間通訊后,線程之間的交互性會更強大,大大提高CPU復用率還會使程序員對各線程任務處理過程中進行有效的把控與監(jiān)督。
1、wait和notify
不使用wait和notify進行線程間通信會發(fā)生什么?

public class TestWait {
    public static void main(String[] args) {
        List list=new ArrayList();
        Thread1 t1=new Thread1(list);
        t1.start();
        Thread2 t2=new Thread2(list);
        t2.start();
    }
}
class Thread1 extends Thread{
    private List list;
    public Thread1(List list){
        this.list=list;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                list.add(i);
                System.out.println("添加了" + (i + 1) + "個元素");
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Thread2 extends Thread{
    //強制線程從公共堆棧中獲取
    volatile private  List list;
    public Thread2(List list){
        this.list=list;
    }

    @Override
    public void run() {
        try {
        while (true){
            if (list.size()==5){
                System.out.println("list已經(jīng)有5個數(shù)據(jù)了,線程B退出");
                throw new InterruptedException();
            }
        }}
        catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

這樣通過在list上加volatile關(guān)鍵可以強制線程2每次都從公共堆棧獲取list,這樣可以實現(xiàn)線程2對線程1操作的監(jiān)控,但是這是一直輪訓的方式。如果輪詢的時間間隔很小,更浪費CPU資源,如果輪詢時間間隔很大,可能會取不到想要的數(shù)據(jù)。所以就需要一種機制減少CPU資源的浪費,而且還可以實現(xiàn)在多個線程間通信。它就是wait和notify。
2、什么是等待和通知機制?
廚師和服務員上菜就是等待和通知的機制。
前面的例子也可以實現(xiàn)通信,原因是多個線程共同訪問同一個變量,但是這種機制不是等待和通知的機制,兩個線程完全是主動式的讀取同一個共享變量,在花費讀取時間的基礎(chǔ)上,讀取到的是不是我們想要的并不確定。所以使用等待和通知的機制滿足開發(fā)的需求。
3、wait/notify機制的實現(xiàn)
wait()方法的作用是使當前正在執(zhí)行的線程進入正在等待的狀態(tài),wait()方法是Object類的方法,該方法是用來將當前線程放入到預執(zhí)行隊列,并且在wait()所在的代碼行停止執(zhí)行,知道接到通知或中斷為止。在調(diào)用wait()方法之前,線程必須獲得該對象的對象鎖,也就是說只能在同步方法或同步代碼塊中調(diào)用wait()方法。如果在執(zhí)行wait()方法后,當前線程鎖會自動釋放。當wait()方法返回后,線程會與其他線程競爭獲得鎖。

public static void main(String[] args) {
        try{
            String s=new String();
            System.out.println("同步代碼塊之前");
          synchronized (s){
              System.out.println("同步代碼塊中wait前");
              s.wait();
              System.out.println("wait之后");
          }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

notify()方法也要在同步方法或者同步代碼塊中使用,在調(diào)用前線程必須獲得該對象的對象鎖,如果沒有獲得對象的鎖,也會拋illegalMonitorStateException。這個方法是用來通知那些可能等待鎖對象的其他線程,如果有多個線程等待,由線程調(diào)度器隨機挑選一個處于該對象wait()狀態(tài)的線程,向其發(fā)出通知,并使等待線程獲取該對象的對象鎖。
在執(zhí)行notify()方法后,當前線程不會馬上釋放該對象鎖,wait狀態(tài)的線程也不能馬上獲得該對象鎖,要等到執(zhí)行notify()方法的線程將任務執(zhí)行完成后,也就是退出synchronized代碼塊之后,當前線程才會釋放鎖,其他wait狀態(tài)的線程才能獲得該對象鎖。
當?shù)谝粋€獲得該對象鎖的wait線程運行完成后,釋放該對象鎖,其他還在wait該對象鎖的線程還會處于阻塞狀態(tài),除非該對象再次調(diào)用notify()方法。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo3ThreadA(lock);
        t1.start();
        Thread.sleep(2000);
        Thread t2=new Demo3ThreadB(lock);
        t2.start();
    }

}
class Demo3ThreadA extends Thread{
    private Object lock;
    public Demo3ThreadA(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        try{
            synchronized (lock){
                System.out.println("線程A開始等待。。"+System.currentTimeMillis());
                lock.wait();
                System.out.println("線程A結(jié)束等待。。"+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo3ThreadB extends Thread{
    private Object lock;
    public Demo3ThreadB(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        try {
        synchronized (lock){
            System.out.println("線程B準備發(fā)出通知。。"+System.currentTimeMillis());
            lock.notify();
            System.out.println("線程B結(jié)束發(fā)出通知。。"+System.currentTimeMillis());
            Thread.sleep(1000);
        }}catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

簡單的說就是wait使線程停止運行,notify使停止的線程繼續(xù)運行。
4、wait()方法自動釋放鎖與notify()方法不會釋放鎖
還可以在上面的代碼,執(zhí)行notify()方法之后,要執(zhí)行完當前的synchronized代碼塊才會釋放對象鎖。執(zhí)行wait()方法是馬上就釋放當前的對象鎖。
5、當wait()方法遇到interrupt方法

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo4Thread(lock);
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}
class Demo4Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println("準備開始等待");
                lock.wait();
                System.out.println("結(jié)束等待");
            }
        }catch (InterruptedException e){
            System.out.println("出現(xiàn)異常是因為wait狀態(tài)的線程別interrupt了");
            e.printStackTrace();
        }
    }
}
class Demo4Thread extends Thread{
    private Object lock;
    public Demo4Thread(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        Demo4Service service=new Demo4Service();
        service.foo(lock);
    }
}

當線程呈wait狀態(tài)時,調(diào)用線程對象的interrupt方法會產(chǎn)生InterruptedException異常。
通過前面的例子總結(jié)出:
(1)執(zhí)行完同步代碼塊就會釋放鎖
(2)在執(zhí)行同步代碼塊的過程中,遇到異常而導致線程終止,鎖也會被釋放。
(3)在執(zhí)行同步代碼塊的過程中,執(zhí)行鎖對象的wait()方法,也會釋放對象鎖,而此線程對象會進入線程等待池中等待被喚醒。

6、只喚醒一個線程或者所有的線程

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo5ThreadA(lock);
        t1.start();
        Thread.sleep(1000);
        Thread t2=new Demo5ThreadA(lock);
        t2.start();
        Thread.sleep(1000);
        Thread t3=new Demo5ThreadB(lock);
        t3.start();
    }
}
class Demo5Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println(Thread.currentThread().getName()+"準備wait釋放鎖");
                lock.wait();
                System.out.println(Thread.currentThread().getName()+"得到了對象鎖");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo5ThreadA extends Thread{
    private Object lock;
    public Demo5ThreadA(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        Demo5Service service=new Demo5Service();
        service.foo(lock);
    }
}
class Demo5ThreadB extends Thread{
    private Object lock;
    public Demo5ThreadB(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        synchronized (lock){
            lock.notifyAll();
//                lock.notify();
//                Thread.sleep(1000);
//                lock.notify();
            System.out.println(Thread.currentThread().getName()+"通知其他線程");
        }
    }
}

調(diào)用notify()方法只會通知一個wait該對象鎖的線程喚醒,調(diào)用多次可以喚醒多個wait該對象鎖的線程,不能保證系統(tǒng)中確定有多少個線程,所以可以使用notifyAll()喚醒所有等待對象鎖的線程。

7、wait(long)的使用
帶一個long參數(shù)的wait方法參數(shù)的作用是等待某一時間內(nèi)是否有其他線程喚醒這個等待的線程,如果超過等待時間會自動喚醒。

    public static void main(String[] args) throws InterruptedException {
        Object lock=new Object();
        Thread t1=new Demo6ThreadA(lock);
        t1.start();
        Thread.sleep(4000);
        Thread t2=new Demo6ThreadB(lock);
        t2.start();
    }
}
class Demo6ThreadA extends Thread{
    private Object lock;
    public Demo6ThreadA(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try{
            synchronized (lock){
                System.out.println("進入同步代碼塊"+System.currentTimeMillis());
                lock.wait(3000);
                System.out.println("喚醒于"+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo6ThreadB extends Thread{
    private Object lock;
    public Demo6ThreadB(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        synchronized (lock){
            lock.notify();
            System.out.println("線程B喚醒其他線程");
        }
    }
}

sleep(long)與wait(long)非常相像,都是在指定時間后線程會自動喚醒,區(qū)別在于sleep是不會釋放對象鎖的,而wait()方法會釋放對象鎖。
8、通知過早
如果通知過早就會打亂程序正常運行。

Object lock=new Object();
        Thread t1=new Demo7ThreadA(lock);//執(zhí)行wait
        Thread t2=new Demo7ThreadB(lock);//執(zhí)行notify
        t2.start();
        Thread.sleep(100);
        t1.start();

解決方法:可以設置一個flag,當執(zhí)行notify()時,設置為false,在線程執(zhí)行wait()之前檢查flag就可以避免notify先運行之后,再又執(zhí)行wait()導致不能被喚醒。注意這個flag變量要是一個對象,不能像String一樣導致對象鎖變化。
9、wait的條件發(fā)生變化
在使用wait/notify模式時,需要注意另外一種情況,就是wait等待的條件發(fā)生了變化,很容易會導致程序邏輯的混亂。

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Demo8Service service=new Demo8Service();
        Thread t1=new Demo8ThreadB(service);
        t1.start();
        Thread t2=new Demo8ThreadB(service);
        t2.start();
        Thread.sleep(3000);
        Thread t3=new Demo8ThreadA(service);
        t3.start();
    }

}
class Demo8Service{
    private List list=new ArrayList();
    private Object lock=new Object();
    public void add(){
        synchronized (lock){
            list.add("a");
            lock.notifyAll();
        }
    }
    public void substrac(){
        try{
            synchronized (lock){
                if (list.size()==0){
                    System.out.println(Thread.currentThread().getName()+"開始等待數(shù)據(jù)");
                    lock.wait();
                    System.out.println(Thread.currentThread().getName()+"結(jié)束等待");
                }
                if (list.size()>0)//不加判斷可能會出錯
                list.remove(0);
                System.out.println(Thread.currentThread().getName()+":list 的大小是"+list.size());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
class Demo8ThreadA extends Thread{
    private Demo8Service service;
    public Demo8ThreadA(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
      service.add();
    }
}
class Demo8ThreadB extends Thread{
    private Demo8Service service;
    public Demo8ThreadB(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.substrac();
    }
}

10、生產(chǎn)者/消費者模式的實現(xiàn)
(1)一個生產(chǎn)者與一個消費者(操作值)

public class Demo9 {
    public static String value="";

    public static void main(String[] args) {
        Object lock=new Object();
        Thread t1=new Demo9producerThread(lock);
        t1.start();
        Thread t2=new Demo9consumerThread(lock);
        t2.start();
    }
}
class Demo9producerThread extends Thread{
    private Object lock;
    public Demo9producerThread(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try {
            while (true){
                synchronized (lock){
                    if (!"".equals(Demo9.value)){
                        lock.wait();
                    }
                    String value=System.currentTimeMillis()+"_"+System.nanoTime();
                    System.out.println("Set的值是"+value);
                    Demo9.value=value;
                    lock.notify();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo9consumerThread extends Thread{
    private Object lock;
    public Demo9consumerThread(Object lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        try{
            while (true){
                synchronized (lock){
                    if ("".equals(Demo9.value)){
                        lock.wait();
                    }
                    System.out.println("Get的值是"+Demo9.value);
                    Demo9.value="";
                    lock.notify();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

如果在這個基礎(chǔ)上設計多生產(chǎn)者和多消費者,在運行過程中很有可能會出現(xiàn)假死的情況,也就是所有的線程都是wait狀態(tài)。
(2)多生產(chǎn)者與多消費者(操作值)

public class Demo10 {
    public static String value="";

    public static void main(String[] args) {
        Object lock=new Object();
        int size=2;
        Thread[] producers=new Thread[size];
        Thread[] consumers=new Thread[size];
        for (int i=0;i<size;i++){
            char c=(char)('A'+i);
            producers[i]=new Demo10producerThread(lock);
            producers[i].setName("生產(chǎn)者"+c);

            consumers[i]=new Demo10consumerThread(lock);
            consumers[i].setName("消費者"+c);

            producers[i].start();
            consumers[i].start();
        }
    }
}
class Demo10V{

}
class Demo10producerThread extends Thread{
    private Object lock;
    public Demo10producerThread(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        try {
            while (true){
                synchronized (lock){
                    if (!"".equals(Demo10.value)){
                        System.out.println(Thread.currentThread().getName()+"等待中");
                        lock.wait();
                    }
                    System.out.println(Thread.currentThread().getName()+"生產(chǎn)中");
                    String value=System.currentTimeMillis()+"_"+System.nanoTime();
                    Demo10.value=value;
                    lock.notifyAll();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo10consumerThread extends Thread{
    private Object lock;
    public Demo10consumerThread(Object lock){
        this.lock=lock;
    }

    @Override
    public void run() {
        try{
            while (true){
                synchronized (lock){
                    if ("".equals(Demo10.value)){
                        System.out.println(Thread.currentThread().getName()+"等待中");
                        lock.wait();
                    }
                    System.out.println(Thread.currentThread().getName()+"消費中");
//                    System.out.println("Get的值是"+Demo9.value);
                    Demo10.value="";
                    lock.notifyAll();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

假設生產(chǎn)者A在生產(chǎn)數(shù)據(jù),其他3個線程(生產(chǎn)者B、消費者A、消費者B)都是呈等待狀態(tài),當生產(chǎn)者A生產(chǎn)完成后,隨機喚醒1個線程,剛好喚醒了生產(chǎn)者B,生產(chǎn)者B發(fā)現(xiàn)value里面有數(shù)據(jù),所以進入等待狀態(tài)(生產(chǎn)者A競爭鎖,生產(chǎn)者B、消費者A、消費者B等待狀態(tài)),A又重新獲得鎖但是它發(fā)現(xiàn)自己之前創(chuàng)建出來的值還沒有被消費,所以又進入等待狀態(tài),結(jié)果是4個線程都處于等待狀態(tài)。怎么解決這個問題?使用notifyall()方法喚醒所有等待的線程,保證生產(chǎn)出來的值會被消費掉。
(3)一生產(chǎn)者和一消費者(操作集合)

public class Demo11 {
    public static void main(String[] args) {
        Demo11V v=new Demo11V();
        Thread producer=new Demo11Producer(v);
        producer.setName("生產(chǎn)者");
        producer.start();
        Thread consumer=new Demo11Consumer(v);
        consumer.setName("消費者");
        consumer.start();
    }

}
class Demo11V{
    private List<String> list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            if (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加數(shù)據(jù)"+val);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            if (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消費數(shù)據(jù) "+returnString);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}
class Demo11Producer extends Thread{
    private Demo11V v;
    public Demo11Producer(Demo11V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}
class Demo11Consumer extends Thread{
    private Demo11V v;
    public Demo11Consumer(Demo11V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

(4)一生產(chǎn)者和多消費者(操作集合)

public class Demo12 {
    public static void main(String[] args) {
        Demo12V v=new Demo12V();
        Thread producer=new Demo12Producer(v);
        producer.setName("生產(chǎn)者");
        producer.start();
        Thread[] consumers=new Thread[5];
        for (int i=0;i<consumers.length;i++){
            consumers[i]=new Demo12Consumer(v);
            consumers[i].setName("消費者"+i);
            consumers[i].start();
        }

    }

}

class Demo12V{
    private List<String> list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            if (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加數(shù)據(jù)"+val);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            while (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消費數(shù)據(jù) "+returnString);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

class Demo12Producer extends Thread{
    private Demo12V v;
    public Demo12Producer(Demo12V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}

class Demo12Consumer extends Thread{
    private Demo12V v;
    public Demo12Consumer(Demo12V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

注意:
1、消費者在判斷集合是否有數(shù)據(jù)時不能使用if,有可能重復喚醒的還是消費者,需要使用while語句判斷,保證集合中一定是有數(shù)據(jù)的
2、消費者喚醒線程不使用notify,notify是隨機喚醒一個線程,如果喚醒的還是消費者,將進入無限等待。所以需要使用notifyAll保證可以喚醒生產(chǎn)者重新生產(chǎn)數(shù)據(jù)
(5)多生產(chǎn)者一個消費者(操作集合)

public class Demo13 {
    public static void main(String[] args) {
        Demo13V v=new Demo13V();
        Thread[] producers=new Thread[5];
        for (int i=0;i<producers.length;i++){
            producers[i]=new Demo13Producer(v);
            producers[i].setName("生產(chǎn)者"+i);
            producers[i].start();
        }
        Thread consumer=new Demo13Consumer(v);
        consumer.setName("消費者");
        consumer.start();
    }

}

class Demo13V{
    private List<String> list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            while (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加數(shù)據(jù)"+val);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            if (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消費數(shù)據(jù) "+returnString);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notify();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

class Demo13Producer extends Thread{
    private Demo13V v;
    public Demo13Producer(Demo13V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}

class Demo13Consumer extends Thread{
    private Demo13V v;
    public Demo13Consumer(Demo13V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

注意:
1、因為有多個生產(chǎn)者,如果要保證集合中始終不超過一個元素,應該使用while判斷,同時生產(chǎn)完數(shù)據(jù)后應該使用notifyAll保證喚醒那一個消費的線程,防止所有線程都處于等待狀態(tài)。
(6)多生產(chǎn)者與多消費者(操作集合)

public class Demo14 {
    public static void main(String[] args) {
        Demo14V v = new Demo14V();
        int size = 5;
        Thread[] producers = new Thread[size];
        Thread[] consumers = new Thread[size];
        for (int i = 0; i < size; i++) {
            producers[i] = new Demo14Producer(v);
            producers[i].setName("生產(chǎn)者" + i);
            producers[i].start();
            consumers[i] = new Demo14Consumer(v);
            consumers[i].setName("消費者" + i);
            consumers[i].start();
        }
    }
}
class Demo14V{
    private List<String> list=new ArrayList<>();
    synchronized public void push(String val){
        try {
            while (list.size()==1){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            list.add(val);
            System.out.println(Thread.currentThread().getName()+"添加數(shù)據(jù)"+val);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
    synchronized public void pop(){
        String returnString;
        try {
            while (list.size()==0){
                System.out.println(Thread.currentThread().getName()+"等待中。。。");
                this.wait();
            }
            returnString=list.get(0);
            list.remove(0);
            System.out.println(Thread.currentThread().getName()+"消費數(shù)據(jù) "+returnString);
            System.out.println(Thread.currentThread().getName()+"還有"+list.size()+"個數(shù)據(jù)");
            this.notifyAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}

class Demo14Producer extends Thread{
    private Demo14V v;
    public Demo14Producer(Demo14V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.push(Math.random()+"");
        }
    }
}

class Demo14Consumer extends Thread{
    private Demo14V v;
    public Demo14Consumer(Demo14V v){
        this.v=v;
    }

    @Override
    public void run() {
        while (true){
            v.pop();
        }
    }
}

3.2join方法

在很多情況下,都由主線程創(chuàng)建并運行子線程,如果子線程中需要進行大量的耗時運行,主線程往往將早于子線程結(jié)束。這時,如果主線程想等待子線程執(zhí)行完成后再結(jié)束,比如子線程處理一個數(shù)據(jù),主線程必須要取得這個數(shù)據(jù)處理的結(jié)果,可以使用join方法。join方法是等待線程對象銷毀。
1、join方法的使用

public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Demo15Thread();
        t1.start();
        t1.join();
        System.out.println("子線程執(zhí)行完成之后再執(zhí)行");
    }
}
class Demo15Thread extends Thread{
    @Override
    public void run() {
        try {
            int val= (int) (Math.random()*10000);
            System.out.println("需要等待"+val+"毫秒");
            Thread.sleep(val);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

join方法可以使所屬的線程對象(子線程)執(zhí)行run方法中的任務,而使當前線程main進行無限期的阻塞。等待子線程銷毀后再繼續(xù)執(zhí)行主線程后面的代碼。方法join具有能使線程排隊運行的作用,有些類似于同步代碼的執(zhí)行效果。
join與synchronized的區(qū)別:
join的內(nèi)部使用wait方法進行等待,而synchronized使用的是對象鎖的機制作為同步。
2、join方法與異常

public class Demo16 {
    public static void main(String[] args) throws InterruptedException {
        Demo16ThreadB t=new Demo16ThreadB();
        t.start();
        Thread.sleep(10);
        Thread t1=new Demo16ThreadC(t);
        t1.start();
    }
}
class Demo16ThreadA extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            //耗時操作
            String s = new String();
            Math.random();
        }
    }
}
class Demo16ThreadB extends Thread {
    @Override
    public void run() {
        try {
            Thread t1 = new Demo16ThreadA();
            t1.start();
            t1.join();
            System.out.println("線程B正常結(jié)束");
        } catch (InterruptedException e) {
            System.out.println("線程B異常結(jié)束");
            e.printStackTrace();
        }
    }
}
class Demo16ThreadC extends Thread {
    private Demo16ThreadB threadB;

    public Demo16ThreadC(Demo16ThreadB threadB) {
        this.threadB = threadB;
    }
    @Override
    public void run() {
        threadB.interrupt();
    }
}

join與interrupt方法如果彼此遇到,則會出現(xiàn)異常,但是進程并沒有結(jié)束,原因是join的所屬線程A還在繼續(xù)運行,A沒有出現(xiàn)異常,還是正常狀態(tài)下執(zhí)行。(就是說如果主線程在等待子線程join結(jié)束的過程中,被interrupt打斷,就會拋出異常)
3、sleep(long)不會釋放鎖,而join(long)底層還是wait,所以會釋放鎖。join(2000)的運行效果和sleep(2000)是一樣的。
4、方法join(long)的功能是在內(nèi)部使用wait(long)來實現(xiàn)同步,所以join(long)具有釋放同步鎖的特點。

3.3 ThreadLocal類

變量值共享可以使用靜態(tài)變量形式,所有的線程都可以使用同一個靜態(tài)變量。如果想使每一個線程都有自己的共享變量如何解決?java提供ThreadLocal類解決這個問題。
ThreadLocal類主要解決每個線程綁定自己的值,可以將ThreadLocal類比喻成全局存儲數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù)。

 public static void main(String[] args) {
        ThreadLocal t1=new ThreadLocal();
        if (t1.get()==null){//獲取當前線程存儲的數(shù)據(jù)
            System.out.println("從未放過值");
            t1.set("a");//保存當前線程的數(shù)據(jù)
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }

ThreadLocal,使用get方法獲取當前線程保存的數(shù)據(jù),使用set方法將數(shù)據(jù)保存到當前線程對于的ThreadLocal。

4、lock

4.1ReetranLock

在java多線程中,可以使用synchronized關(guān)鍵字來實現(xiàn)線程之間的同步互斥,但在jdk1.5中新增加ReentranLock類也可以達到同樣的效果,并且在擴展功能上更加強大,比如具有嗅探鎖定、多路分支通知等,而且在使用上也比synchronized更加靈活。

4.1.1ReentranLock的使用

public class Demo1 {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
        Thread t1=new Demo1Thread(lock);
        Thread t2=new Demo1Thread(lock);
        Thread t3=new Demo1Thread(lock);
        t1.start();
        t2.start();
        t3.start();
    }
}
class Demo1Thread extends Thread{
    private Lock lock;
    public  Demo1Thread(Lock lock){
        this.lock=lock;
    }
    @Override
    public void run() {
        lock.lock();//加上同步鎖
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+","+(i+1));
        }
        lock.unlock();//解開同步鎖
    }
}

當前線程打印完畢后將鎖釋放,其他線程才可以繼續(xù)打印。
調(diào)用lock()方法的線程就會持有對象鎖,其他線程只能等待鎖被釋放(調(diào)用unlock())才可以再次爭搶鎖,效果和使用synchronized關(guān)鍵字一樣,線程之間還是按照順序執(zhí)行。
2、ReentrantLock實現(xiàn)wait和notify
使用synchronized和wait/notify可以實現(xiàn)等待/通知模式。ReentrantLock類也可以實現(xiàn)同樣的功能,但需要借助Condition對象。Condition是jdk5出現(xiàn)的技術(shù),使用它會有更好的靈活性,比如可以實現(xiàn)多路通知功能,也就是在一個Lock對象里面可以創(chuàng)建多個Condition實例,線程對象可以注冊在指定的Condition中,從而可以有選擇地進行線程通知,在通知線程上更加靈活。
在使用wait/notifyAll方法進行通知的時候,被通知的線程是JVM隨機選擇的,但使用ReentrantLock結(jié)合Condition類可以實現(xiàn)選擇性通知,這個功能是非常重要的,而且在Condition類中是默認提供的。
而synchronized就相當于整個Lock對象中就只有一個Condition對象,所有的線程都是注冊在一個Condition對象上的,所以只能在notifyAll的時候是沒有選擇權(quán)的,會出現(xiàn)效率問題。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Demo3Service service = new Demo3Service();
        Thread t1 = new Demo3Thread(service);
        t1.start();
        Thread.sleep(2000);
        service.signal();
    }
}

class Demo3Service {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await方法開始于" + System.currentTimeMillis());
            condition.await();//需要在同步代碼中調(diào)用
            System.out.println("await方法結(jié)束于" + System.currentTimeMillis());
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void signal() {
        lock.lock();
        System.out.println("signal方法開始于" + System.currentTimeMillis());
        condition.signal();//需要在同步代碼中調(diào)用
        lock.unlock();
    }
}

class Demo3Thread extends Thread {
    private Demo3Service service;

    public Demo3Thread(Demo3Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.await();
    }
}

Condition 是通過ReentrantLock對象創(chuàng)建的,condition有await()方法和signal()方法,分別用來等待和通知,注意他們兩個也是必須在同步代碼中使用。
3、使用Condition喚醒不同的線程
分別喚醒不同的線程,就需要使用多個condition 對象,也就是Condition對象可以喚醒部分指定的線程,有助于提高程序的運行效率。

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Demo4Service service=new Demo4Service();
        Thread t1=new Demo4ThreadA(service);
        t1.start();
        Thread t2=new Demo4ThreadB(service);
        t2.start();
        Thread.sleep(2000);
        service.signalAll_B();
    }
}
class Demo4Service{
    private Lock lock=new ReentrantLock();
    private Condition conditionA=lock.newCondition();
    private Condition conditionB=lock.newCondition();
    public void awaitA(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"開始執(zhí)行awaitA方法"+System.currentTimeMillis());
            conditionA.await();
            System.out.println(Thread.currentThread().getName()+"結(jié)束執(zhí)行awaitA方法"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void awaitB(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"開始執(zhí)行awaitB方法"+System.currentTimeMillis());
            conditionB.await();
            System.out.println(Thread.currentThread().getName()+"結(jié)束執(zhí)行awaitB方法"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signalAll_B(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"喚醒所有的線程在"+System.currentTimeMillis());
            conditionB.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
class Demo4ThreadA extends Thread{
    private Demo4Service service;
    public Demo4ThreadA(Demo4Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.awaitA();
    }
}
class Demo4ThreadB extends Thread{
    private Demo4Service service;
    public Demo4ThreadB(Demo4Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.awaitB();
    }
}

使用不同的condition對象調(diào)用await()方法。
5、使用ReentrantLock實現(xiàn)生產(chǎn)者/消費者

public class Demo5 {
    public static void main(String[] args) {
        Demo5Service service=new Demo5Service();
//        Thread t1=new Demo5Producer(service);
//        t1.start();
//        Thread t2=new Demo5Consumer(service);
//        t2.start();
        int size=5;
        Thread[] producers=new Thread[size];
        Thread[] consumers=new Thread[size];
        for (int i=0;i<size;i++){
            producers[i]=new Demo5Producer(service);
            producers[i].setName("producer"+i);
            producers[i].start();
            consumers[i]=new Demo5Consumer(service);
            consumers[i].setName("consumer"+i);
            consumers[i].start();
        }
    }
}
class Demo5Service{
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();
    private String val="";
    public void set(){
        try {
            lock.lock();
            while (!"".equals(val)){
                condition.await();
            }
            val=System.currentTimeMillis()+"-"+System.nanoTime();
            System.out.println(Thread.currentThread().getName()+": 生產(chǎn)值: "+val);
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void get(){
        try {
            lock.lock();
            while ("".equals(val)){
                condition.await();
            }
            System.out.println(Thread.currentThread().getName()+" :消費值: "+val);
            val="";
            condition.signalAll();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
class Demo5Producer extends Thread{
    private Demo5Service service;
    public Demo5Producer(Demo5Service service){
        this.service=service;
    }

    @Override
    public void run() {
       while (true){
           service.set();
       }
    }
}
class Demo5Consumer extends Thread{
    private Demo5Service service;
    public Demo5Consumer(Demo5Service service){
        this.service=service;
    }

    @Override
    public void run() {
        while (true){
            service.get();
        }
    }
}

以上是ReentrantLock實現(xiàn)多生產(chǎn)者多消費者模式。
6、公平鎖與非公平鎖。
Lock分為公平鎖和非公平鎖。公平鎖表示線程獲得鎖的順序是按照加鎖的順序來分配的,也就是先來先得。而非公平鎖就是一種鎖的搶占機制,是隨機獲得鎖,與公平鎖不一樣的就是先來的不一定先得到鎖,這種方法可能會導致某些線程一直拿不到鎖,導致不公平的現(xiàn)象。

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Demo6Service service=new Demo6Service(true);
        Thread[] threads=new Thread[20];
        for (int i=0;i<threads.length;i++){
            threads[i]=new Demo6Thread(service);
        }
        for (int i=0;i<threads.length;i++){
            threads[i].start();
        }
    }
}
class Demo6Service{
    private Lock lock;
    public Demo6Service(boolean isFair){
        //無參的ReentrantLock是非公平鎖,通過Boolean參數(shù)控制鎖的類型,true會創(chuàng)建公平鎖,false則會創(chuàng)建非公平鎖
        lock=new ReentrantLock(isFair);
    }
    public void foo(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"獲得鎖定");

        }finally {
            lock.unlock();
        }
    }
}
class Demo6Thread extends Thread{
    private Demo6Service service;
    public Demo6Thread(Demo6Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.foo();
    }
}

7、實現(xiàn)線程的順序執(zhí)行

public class Demo7 {
   private static Lock lock=new ReentrantLock();
   private static Condition condition1=lock.newCondition();
   private static Condition condition2=lock.newCondition();
   private static Condition condition3=lock.newCondition();
   volatile private static int nextPrintWho=1;
   public static void main(String[] args) {
       Thread t1=new Thread(){
           @Override
           public void run() {
               try {
                lock.lock();
                while (nextPrintWho!=1){
                    condition1.await();}
                for (int i=0;i<3;i++){
                    System.out.println("線程1打印第"+(i+1)+"次");
                }
                nextPrintWho=2;
                condition2.signalAll();
               }catch (InterruptedException e){
                   e.printStackTrace();
               }finally {
                   lock.unlock();
               }
           }
       };
       Thread t2=new Thread(){
           @Override
           public void run() {
               try {
                   lock.lock();
                   while (nextPrintWho!=2){
                       condition2.await();}
                   for (int i=0;i<3;i++){
                       System.out.println("線程2打印第"+(i+1)+"次");
                   }
                   nextPrintWho=3;
                   condition3.signalAll();
               }catch (InterruptedException e){
                   e.printStackTrace();
               }finally {
                   lock.unlock();
               }
           }
       };
       Thread t3=new Thread(){
           @Override
           public void run() {
               try {
                   lock.lock();
                   while (nextPrintWho!=3){
                       condition3.await();}
                   for (int i=0;i<3;i++){
                       System.out.println("線程3打印第"+(i+1)+"次");
                   }
                   nextPrintWho=1;
                   condition1.signalAll();
               }catch (InterruptedException e){
                   e.printStackTrace();
               }finally {
                   lock.unlock();
               }
           }
       };

       Thread[] threads1=new Thread[5];
       Thread[] threads2=new Thread[5];
       Thread[] threads3=new Thread[5];
       for (int i=0;i<threads1.length;i++){
           threads1[i]=new Thread(t1);
           threads2[i]=new Thread(t2);
           threads3[i]=new Thread(t3);
           threads1[i].start();
           threads2[i].start();
           threads3[i].start();
       }
   }
}

4.2ReentrantLockReadWriteLock

ReentrantLock具有完全互斥排他鎖,也就是同一時間內(nèi)只有一個線程可以在執(zhí)行l(wèi)ock()方法后面的任務。這樣雖然可以保證實例變量的線程安全性,但是效率卻是非常的低下。所以在jdk中提供了一種讀寫鎖ReentrantLockReadWriteLock類,使用它可以加快運行的效率,在某些不需要操作實例變量的方法中,完全可以使用讀寫鎖來提高方法的執(zhí)行效率。
讀寫鎖表示有兩個鎖,一個是讀鎖也叫共享鎖,另一個寫鎖也叫排他鎖。多個讀鎖之間不互斥,讀鎖與寫鎖是互斥,寫鎖與寫鎖之間也是互斥的。在沒有線程進行寫入操作時,進行讀取操作的多個線程都可以獲得讀取鎖,而進行寫入操作時只有在獲取寫鎖之后才能進行寫入操作。即可以有多個線程同時進行讀取操作,但同一時間只能有一個線程進行寫操作。

public class Demo8 {
    public static void main(String[] args) {
        Demo8Service service=new Demo8Service();
        Thread t1=new Demo8ThreaA(service);
        t1.setName("讀線程A");
        t1.start();
        Thread t2=new Demo8ThreaA(service);
        t2.setName("讀線程B");
        t2.start();
        Thread t3=new Demo8ThreaB(service);
        t3.setName("寫線程B");
        t3.start();
    }

}
class Demo8Service {
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"獲得讀鎖:"+System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"解除讀鎖:"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }

    }
    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+"獲得寫鎖:"+System.currentTimeMillis());
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"解除寫鎖:"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}
class Demo8ThreaA extends Thread{
    private Demo8Service service;
    public Demo8ThreaA(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.read();
    }
}
class Demo8ThreaB extends Thread{
    private Demo8Service service;
    public Demo8ThreaB(Demo8Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.write();
    }
}

讀寫或?qū)懽x操作都是互斥的,只要出現(xiàn)寫操作的過程,就是互斥的,讀讀是共享的。

5、知識補漏

5.1線程的狀態(tài)

1、new :尚未啟動的線程
2、runnable :在和Java虛擬機中正在執(zhí)行的線程的狀態(tài)
3、blocked :被阻塞等待對象鎖的線程
4、waiting :正在等待另一個線程執(zhí)行特定動作的線程
5、time_waiting: 正在等待另一個線程執(zhí)行達到指定等待時間的線程
6、terminated :已經(jīng)退出運行的線程

5.2Callable接口

Runnable接口是執(zhí)行工作的獨立任務,但是它不返回任何值。但是如果希望任務在完成的同時能夠返回一個值,可以通過實現(xiàn)Callable接口。在jdk5中引入Callable接口是一種具有類型參數(shù)的泛型,它的類型參數(shù)表示從反方法call中返回值的類型。

public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable=new Demo2Callable();
        FutureTask<Integer> task=new FutureTask<>(callable);
        Thread t=new Thread(task);
        t.start();
        System.out.println("線程返回的值是"+task.get());
    }
}
class Demo2Callable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"調(diào)用了Callable接口的實現(xiàn)類");
        int val=(int)(Math.random()*10);
        System.out.println("準備返回的值是"+val);
        return val;
    }
}

5.3線程池

線程池的作用:就是限制在系統(tǒng)中可以執(zhí)行的線程的數(shù)量。根據(jù)系統(tǒng)環(huán)境的情況,自動或者手動設置線程數(shù)量,以達到運行的最佳效果。
用線程池控制線程的數(shù)量,多余的線程必須排隊等候,一個任務執(zhí)行完畢,再從隊列中取最前面的任務開始執(zhí)行。如果隊列中沒有等待的線程,線程池的資源就處于等待狀態(tài)。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行,否則任務進入等待隊列。
為什么要使用線程池?
1、減少創(chuàng)建銷毀線程的次數(shù),每個工作線程都可以被重復利用,可執(zhí)行多個任務。
2、可以根據(jù)系統(tǒng)的承受能力調(diào)整線程池中工作線程的數(shù)量,防止因為消耗過多的內(nèi)存,導致服務器崩潰。
java里面線程池的頂級接口是Executor,但嚴格意義來講,Executor并不是一個線程池,而是一個執(zhí)行線程的工具。
比較重要的幾個類:
ExecutorService:真正的線程池接口
ScheduleExecutorService:解決那些需要任務重復執(zhí)行的問題
ThreadPoolExecutor:ExecutorService的默認實現(xiàn)
ScheduleThreadPoolExecutor :繼承ThreadPoolExecutor,實現(xiàn)ScheduleExecutorService接口的類,周期性任務調(diào)試的類的實現(xiàn)。

Executor類里面提供了一些靜態(tài)工廠,生成一些常用的線程池。
-newCachedThreadPool(ThreadPoolExecutor):創(chuàng)建一個可緩存的線程池,如果線程池的大小超過了處理任務所需要的線程,就會回收部分空閑的線程(60秒沒有執(zhí)行的線程),當任務的數(shù)量增加時,又可以智能的添加新的線程來處理任務。這個線程池不會對線程池的大小進行限制,線程池的大小完全依賴于操作系統(tǒng)(JVM)能夠創(chuàng)建的最大的線程數(shù)。
-newFixedThreadPool(ThreadPoolExecutor):創(chuàng)建固定大小的線程池。每次提交一個任務就創(chuàng)建一個線程,直到線程池內(nèi)線程數(shù)達到最大數(shù)量。線程的數(shù)量達到最大后就會保持不變,如果某個線程因為執(zhí)行而異常結(jié)束,那么線程池會補充一個新的線程。
-newSingleThreadExecutor(ThreadPoolExecutor):創(chuàng)建一個單線程的線程池。這個線程池只有宇哥工作線程,也就是相當于單線程串行的執(zhí)行所有任務。如果這個唯一的線程因為異常結(jié)束,那么會有一個新的線程替代它,保證所有任務的執(zhí)行順序,會按照任務的提交順序執(zhí)行。

-newScheduleThreadPool(ScheduleExecutorService):創(chuàng)建一個無限大小的線程池,這個線程池支持定時以及周期性執(zhí)行任務的需求。
-newSingleThreadScheduleExecutor(ScheduleExecutorService):創(chuàng)建一個單線程用于定時以及周期性執(zhí)行任務。
代碼里面怎么用線程池?

public class Demo3 {
    public static void main(String[] args) {
        Runnable r=new Demo3Runnable();
//        ExecutorService service=Executors.newCachedThreadPool();
//        ExecutorService service=Executors.newFixedThreadPool(2);
//        ExecutorService service=Executors.newSingleThreadExecutor();
        //execute就是執(zhí)行代碼
//        service.execute(r);
        //關(guān)閉線程池
//        service.shutdown();
//        for (int i=0;i<10;i++){
//            service.execute(r);
//        }
        ScheduledExecutorService service=Executors.newScheduledThreadPool(2);
        System.out.println(Thread.currentThread().getName()+"準備執(zhí)行"+System.currentTimeMillis());
        //延遲執(zhí)行
        service.schedule(r,3, TimeUnit.SECONDS);
        //定時執(zhí)行()scheduleAtFixedRate(需要執(zhí)行的線程,第一個需要執(zhí)行的線程延遲多長時間執(zhí)行,兩個線程間相隔的時間,時間單位)
        service.scheduleAtFixedRate(r,2,5,TimeUnit.SECONDS);
        //關(guān)閉線程池
        service.shutdown();
    }
}
class Demo3Runnable implements Runnable{
    static long waitTime=1000;
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+"開始于"+System.currentTimeMillis());
            synchronized (this){
                waitTime+=100;
            }
            Thread.sleep(waitTime);
            System.out.println(Thread.currentThread().getName()+"結(jié)束于"+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }

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

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