Java基礎之多線程

什么是多線程?

? 線程是指程序運行的流程,多線程則是指可以運行一個以上線程的程序,多線程使程序運行的效率變得更高。

public class ThreadDemo {
   public static void main(String[] args) {
       new testThread().run();
       for (int i = 0; i < 5; i++) {
           System.out.println("main線程在運行");
       }
   }
}
class testThread {
   public void run() {
       for (int i = 0;i<5;i++) {
           System.out.println("testThread 在運行");
       }
   }
}

? 以上代碼可以看出,要想運行main方法里面的循環(huán)必需要等testThread方法里面的run方法執(zhí)行完了才能運行,這便是單一線程的缺陷,執(zhí)行效率低。要想多個線程同時運行那么就得在Java里激活多個線程。

通過繼承Thread來創(chuàng)建線程

如何在Java里激活多個線程?

? 1. 線程必須擴展自Thread類,使自己成為他的子類。
? 2. 線程的處理必須編寫在run()方法內。

public class ThreadDemo{
    public static void main(String[] args) {
        new ThreadTest().start();
        for(int i = 10; i > 0; i--) {
            System.out.println("線程名稱:" + Thread.currentThread().getName() + ", "+i);
        }
    }
}
class ThreadTest extends Thread{
    public void run() {
        for(int i = 10; i > 0; i--) {
            System.out.println("線程名稱:" + Thread.currentThread().getName() + ", "+ i);
        }
    }
}

?以上代碼運行的結果為

線程名稱:main, 10
線程名稱:Thread-0, 10
線程名稱:main, 9
線程名稱:main, 8
線程名稱:main, 7
線程名稱:Thread-0, 9
線程名稱:main, 6
線程名稱:main, 5
線程名稱:main, 4
線程名稱:Thread-0, 8
線程名稱:main, 3
線程名稱:Thread-0, 7
線程名稱:main, 2
線程名稱:Thread-0, 6
線程名稱:main, 1
線程名稱:Thread-0, 5
線程名稱:Thread-0, 4
線程名稱:Thread-0, 3
線程名稱:Thread-0, 2
線程名稱:Thread-0, 1

?上述結果發(fā)現兩行輸出的結果是沒有順序的,出現這樣的結果是因為main方法也是一個線程。在java中所有的線程都是同時啟動的,至于什么時候,哪個線程先執(zhí)行,完全是由CPU決定的,這就得看誰先獲得CPU資源了。實際上在命令行中運行Java命令時,就啟動了一個JVM進程,默認情況下此進程會產生兩個線程:一個是main方法線程,另外一個就是垃圾回收(GC)線程。

通過實現Runnable接口來創(chuàng)建線程

class 類名 implements Runnable{
    @Override
    public void run() {
        ····
    }
}

警告??
?在執(zhí)行線程的時候,不要是調用run方法,而應該調用Thread.start方法,因為直接調用run方法只會執(zhí)行同一個線程中的任務,而不會啟動新線程。調用Thread.start方法才會創(chuàng)建一個執(zhí)行run方法的新線程。


Thread類和Runnable接口創(chuàng)建線程的比較

?Thread類其實是實現了Runnable接口,也就是說Thread類是Runnable接口的一個子類,用Thread類無法達到資源共享的目的,如果實現了Runable接口的話,則很容易的實現資源共享。而就Runable接口相對于Thread類來說,有幾個顯著的優(yōu)勢:
?1. 適合多個程序代碼的線程去處理廳以資源的情況。
?2. 避免Java的單繼承特性帶來的局限。因為Java程序只允許單一繼承,一個子類只能有一個父類。
?3. 增強了程序的健壯性,代碼能夠被多個線程共享,代碼與數據是獨立的。


線程狀態(tài)

?任何一種線程一般都有5種狀態(tài),即創(chuàng)建,就緒,運行,阻塞,終止。在給定時間點上,一個線程只能處于一種狀態(tài)。要確定一個線程的當前狀態(tài),可調用getState方法。
??(1). New:尚未啟動的線程處于新創(chuàng)建的狀態(tài),即新創(chuàng)建一個線程對象。
??(2). Runnable:調用Thread.start方法的線程處于可運行狀態(tài)。在任何給定時刻,一個可運行的線程可能正在運行也可能沒有運行,這取決于操作系統(tǒng)給線程提供運行的時間。
??(3). Blocked:受阻塞并等待某個監(jiān)視器鎖的線程處于被阻塞狀態(tài)。
??(4). Waiting:無限期的等待另一個線程來執(zhí)行某一特定操作的線程處于等待狀態(tài)。
??(5). Timed waiting:等待另一個線程來執(zhí)行取決于指定等待時間的操作線程處于計時等待狀態(tài)。
??(6). Terminated:已退出的線程處于被終止狀態(tài)。線程有兩原因被終止:
????(1). 因為run方法正常退出而自然死亡。
????(2). 因為一個沒有捕獲的異常終止了run方法而意外死亡。

線程狀態(tài)

線程操作的一些方法

?操作線程的主要方法在Thread類中。以下列舉了一些方法:
??Thread.currentThread().getName():返回現在正在執(zhí)行的線程的名稱。
??isAlive(): 判斷一個線程是否已經啟動而且仍然在啟動,返回Boolean 類型。
??join(): 等待線程終止。強制運行完調用join()方法的線程后再運行其他的線程。而其他線程處于阻塞狀態(tài),當運行完調用join()方法的線程后,其他線程變?yōu)榭蓤?zhí)行狀態(tài)。
?? isInterrupted():判斷目前線程是否被中斷,返回Boolean類型。也可以使用interrupted()方法來判斷當前線程是否被中斷,但是interrupted()方法時候靜態(tài)的方法,調用該方法會產生一個副作用,那就是將當前線程的中斷狀態(tài)重置為false。
??setName():設置線程名稱。
??sleep(long millis): 使目前正在執(zhí)行的線程休眠millis毫秒。線程轉到阻塞狀態(tài),休眠時間結束之后,線程狀態(tài)處于可運行狀態(tài)。
??sleep():使一個正在運行的線程處于睡眠狀態(tài),是一個靜態(tài)方法。此方法會拋出InterruptedException異常,用try...catch捕獲。不會釋放對象鎖。

public class ThreadDemo{
    public static void main(String[] args) {
        ThreadTest te = new ThreadTest();
        Thread t = new Thread(te);
        System.out.println(t.getName()+"線程是否已經啟動  "+t.isAlive());//判斷線程是否已經啟動,在start()方法之前線程未啟動,在start()方法之后線程啟動
        t.start();//啟動線程
        int m = 0;
        if(!Thread.currentThread().isInterrupted()) {//判斷線程是否被中斷
            for(int i = 20; i > 0; i--) {
                if(m == 5) {
                    try {
                        t.join();//強制運行完該線程再運行后面的線程
                    } catch (InterruptedException e) {
                        System.out.println(e.getMessage());
                    }
                }
                System.out.println("main線程名稱:" + Thread.currentThread().getName() + ", "+i + ",    m="+m++);//獲取當前運行線程的名稱
            }
        }
    }
}
class ThreadTest implements Runnable{
    private int tickets = 20 ;
    @Override
    public void run() {
        for(int i = tickets; i > 0; i--) {
            System.out.println("剩余演唱會門票: "+ i+ "張   線程名稱"+Thread.currentThread().getName());
            try {
                Thread.sleep((int) (Math.random() * 10));//目前正在執(zhí)行的線程休眠隨機毫秒,當休眠的時間長一些,在打印的時候會明顯看出來隔了一段時間才繼續(xù)執(zhí)行后面的線程。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

??yiels():將目前正在執(zhí)行的線程處于讓步狀態(tài),把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。這是一個靜態(tài)方法。
??setPriority(int newPriority):設置線程的優(yōu)先級,默認為Thread.NORM_PRIORITY優(yōu)先級,默認值為5。最小優(yōu)先級和最大優(yōu)先級的值分別為1和10。
??setDaemon(Boolean isDaemon):標識該線程為守護線程或者用戶線程,這一方法必須在線程啟動之前調用。

不推薦使用方法
?suspend():暫停線程?和?resume():恢復線程,這兩種方法會導致死鎖的發(fā)生,并且他們可以通過一個線程直接控制另外一個線程的代碼來控制另外一個線程,所以這兩種方法不推薦使用。
?stop():停止線程,雖然stop能夠避免死鎖的發(fā)生,但是如果一個線程操作沒有完成就被“stop”了,將會導致數據的不完整性。


sleep()和yield()的區(qū)別
??sleep()使當前線程進入休眠狀態(tài),所有執(zhí)行sleep()方法的線程進入了不可運行狀態(tài),在指定的時間內不會被執(zhí)行;yield()使當前正在執(zhí)行的線程處于讓步狀態(tài),即重新回到可運行狀態(tài),所以執(zhí)行yield()的線程有可能在進入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。如果有其他的可運行的線程具有至少與此線程相同的優(yōu)先級那么這些線程接下來會被調度。


線程同步

什么是線程同步?

?要想解決數據共享問題,必須使用同步,所謂同步就是指多個線程在同一個時間段內只能有一個線程執(zhí)行指定代碼,其他線程要等待此線程完成之后才可以繼續(xù)執(zhí)行。可以將同步理解為“排隊”執(zhí)行。

線程同步有兩種方法:
?1.同步方法:有synchronized關鍵字修飾的方法。當有一個線程進入了synchronized關鍵字修飾的方法,其他線程便不能進入,知道該線程執(zhí)行該方法為止。同步方法實際上同步的是this 對象。

public synchronized void methodName(){
   代碼操作;
}
等同于
synchronized(this){
    代碼操作;
}

?2.同步代碼塊:有synchronized關鍵字修飾的語句塊。在同一時刻只能有一個線程進入同步代碼塊內進行運行,只有該線程離開同步代碼塊后,其他線程才能進入同步代碼塊內。

synchronized(任意對象){
    代碼操作;
}

e.g.:一般方法

package test;

public class SynchronizedForLock {

    public static void main(String[] args) {
        Emloyee emloyee = new Emloyee( "1", 1000);
        new Thread(new OutMoney("琪琪",600,emloyee)).start();
        new Thread(new OutMoney("岳岳",500,emloyee)).start();
        new Thread(new OutMoney("琴琴",400,emloyee)).start();

    }
}
class OutMoney implements Runnable {
    private double outMoney;
    private Emloyee emloyee;
    private String name;
    
    public OutMoney(String name,double outMoney, Emloyee emloyee) {
        super();
        this.outMoney = outMoney;
        this.emloyee = emloyee;
        this.name = name;
    }

    @Override
    public void run() {
            double liveMoney = emloyee.getAllMoney() - outMoney;
            if(liveMoney >= 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                emloyee.setAllMoney(liveMoney);
                System.out.println("線程名稱:" + Thread.currentThread().getName()  + "  "+name+"取錢  ¥"+ outMoney+"  余額: ¥"+ liveMoney);
            }else {
                System.out.println("取錢失敗,余額不足");  
            }
        }
    
}

運行結果

線程名稱:Thread-2  琴琴取錢  ¥400.0  余額: ¥600.0
線程名稱:Thread-0  琪琪取錢  ¥600.0  余額: ¥400.0
線程名稱:Thread-1  岳岳取錢  ¥500.0  余額: ¥500.0

?由運行結果可以看出四個線程同時運行,但是由剩余的余額看出運行的總額數是1000,這是因為資源數據不同步引起的,這就需要線程同步來解決這個問題了。

方法一:同步代碼塊
package test;

public class SynchronizedForLock {

    public static void main(String[] args) {
        Emloyee emloyee = new Emloyee( "1", 1000);
        new Thread(new OutMoney("琪琪",600,emloyee)).start();
        new Thread(new OutMoney("岳岳",500,emloyee)).start();
        new Thread(new OutMoney("琴琴",400,emloyee)).start();

    }
}
class OutMoney implements Runnable {
    private double outMoney;
    private Emloyee emloyee;
    private String name;
    
    public OutMoney(String name,double outMoney, Emloyee emloyee) {
        super();
        this.outMoney = outMoney;
        this.emloyee = emloyee;
        this.name = name;
    }

    @Override
    public void run() {
        synchronized (emloyee) {//同步代碼塊
            double liveMoney = emloyee.getAllMoney() - outMoney;
            if(liveMoney >= 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                emloyee.setAllMoney(liveMoney);
                System.out.println("線程名稱:" + Thread.currentThread().getName()  + "  "+name+"取錢  ¥"+ outMoney+"  余額: ¥"+ liveMoney);
            }else {
                System.out.println("取錢失敗,余額不足");  
            }
        }
        
    }
    
}
方法二:同步方法
public class SynchronizedForLock {

    public static Object LOCK = new Object();

    public static void main(String[] args) {
        Emloyee emloyee = new Emloyee("1", 1000);
        OutMoney outMoney0 = new OutMoney("琪琪", 600, emloyee);
        OutMoney outMoney1 = new OutMoney("岳岳", 400, emloyee);
        OutMoney outMoney2 = new OutMoney("琴琴", 500, emloyee);

        new Thread(outMoney0).start();
        new Thread(outMoney1).start();
        new Thread(outMoney2).start();

    }

    static class OutMoney implements Runnable {
        private double outMoney;
        private Emloyee emloyee;
        private String name;

        public OutMoney(String name, double outMoney, Emloyee emloyee) {
            super();
            this.outMoney = outMoney;
            this.emloyee = emloyee;
            this.name = name;
        }

        @Override
        public void run() {
            emloyee.outMoneySy(outMoney,name);//同步方法
        }
}
Emloyee類
public class Emloyee {
    private String num;
    private String name ;
    private double allMoney;
    public Emloyee(String num, double allMoney) {
        super();
        this.num = num;
        this.allMoney = allMoney;
    }
    public String getNum() {
        return num;
    }
    public void setNum(String num) {
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getAllMoney() {
        return allMoney;
    }
    public void setAllMoney(double liveMoney) {
        this.allMoney = liveMoney;
    } 
    public synchronized void outMoneySy(double outMoney,String name) {
        double liveMoney = allMoney - outMoney;
        if (liveMoney >= 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            allMoney = liveMoney;
            System.out.println("線程名稱:" + Thread.currentThread().getName() + "  " + name + "取錢  ¥" + outMoney+ "  余額: ¥" + liveMoney);
        } else {
            System.out.println("取錢失敗,余額不足");
        }
    }
}

?運行結果

線程名稱:Thread-0  琪琪取錢  ¥600.0  余額: ¥400.0
取錢失敗,余額不足
線程名稱:Thread-1  岳岳取錢  ¥400.0  余額: ¥0.0

?同步代碼塊有synchronized (this)用法,還有synchronized (非this對象)【例如上面同步代碼塊例子】以及synchronized (類.class)這兩種用法,而使用不同的同步代碼塊的方法,它們的含義也是不同的。

? synchronized修飾非靜態(tài)方法時,同步代碼塊的synchronized (this)和synchronized (非this對象)的鎖是對象,(this指的是什么呢?它指的就是調用這個方法的對象)線程要執(zhí)行對應同步代碼,需要獲得相應的對象鎖。而同步代碼塊的synchronized (類.class)用法鎖的是類,線程想要執(zhí)行對應同步代碼,需要獲得相應的類鎖。

? synchronized修飾靜態(tài)方法時,同步方法只能使用類鎖和靜態(tài)對象,而非靜態(tài)同步方法可以是類鎖或者任何對象。因為在加載類文件的時候,靜態(tài)同步方法由于是靜態(tài)的也被加載進內存了,類名.class的加載優(yōu)先級高于靜態(tài)方法。

?有synchronized關鍵字修飾的方法或者代碼塊來實現加鎖,并且同步的范圍越大,性能就越差。

?Java是通過object類的wait(),notify(),notityAll ()這幾個方法來實現線程之間的通信的,而所有的類都是從object繼承的所以任何類都可以直接使用這些方法。并且這些方法只能在synchronized方法中調用。
??(1) . wait():使一個線程處于等待狀態(tài),直到其他線程調用該同步監(jiān)視器的notify()方法或notifyAll()方法來喚醒線程,調用wait()方法的當前線程會釋放對該同步監(jiān)視器(鎖)的鎖定。
??(2) . notify():喚醒一個處于等待狀態(tài)的某一個線程,注意的是在調用此方法的時候,并不能確切的
喚醒某一個等待狀態(tài)的線程,而是由JVM確定喚醒哪個線程,而且不是由線程的優(yōu)先級決定。
??(3) . notityAll ():喚醒所有處入等待狀態(tài)的線程,注意并不是給所有喚醒線程一個對象的鎖,
而是讓它們競爭。具有最高優(yōu)先級的線程最先被喚醒并執(zhí)行。
e.g.

package test;
public class SynchronizedForLock {
    public static void main(String[] args) throws InterruptedException {
        Emloyee emloyee = new Emloyee("1", 1000);
        OutMoney outMoney0 = new OutMoney("琪琪", 600, emloyee);
        OutMoney outMoney1 = new OutMoney("岳岳", 400, emloyee);
        OutMoney outMoney2 = new OutMoney("琴琴", 500, emloyee);

        OutMoney none = new OutMoney(null, -1, emloyee);
        new Thread(outMoney0,"#1---").start();
        new Thread(outMoney1,"#2---").start();
        new Thread(outMoney2,"#3---").start();
        Thread.sleep(2000);
        new Thread(none,"#4---").start();
    }

    static class OutMoney implements Runnable {
        private double outMoney;
        private Emloyee emloyee;
        private String name;

        public OutMoney(String name, double outMoney, Emloyee emloyee) {
            super();
            this.outMoney = outMoney;
            this.emloyee = emloyee;
            this.name = name;
        }

        @Override
        public void run() {
                emloyee.outMoneySy(outMoney,name);//同步方法
        }
            
    }

}

Emloyee類

package test;
public class Emloyee {
    private String num;
    private String name ;
    private double allMoney;
    public Emloyee(String num, double allMoney) {
        super();
        this.num = num;
        this.allMoney = allMoney;
    }
    public String getNum() {
        return num;
    }
    public void setNum(String num) {
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getAllMoney() {
        return allMoney;
    }
    public void setAllMoney(double liveMoney) {
        this.allMoney = liveMoney;
    } 
     
//   同步方法
    Boolean threadState = true;
    public synchronized void outMoneySy(double outMoney,String name){
        if(name == null)
        {
            notifyAll();
            return ;
        }
        double liveMoney = allMoney - outMoney;
        if(liveMoney >= 0) {
            while(!threadState) {
                try {
                    System.out.println("線程名稱:" + Thread.currentThread().getName() + "被等待  ");
                    this.wait();
                    threadState = true ;
                    System.out.println("線程名稱:" + Thread.currentThread().getName() + "在wait后執(zhí)行了該方法");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            allMoney = liveMoney;
            System.out.println("線程名稱:" + Thread.currentThread().getName() + "  " + name + "取錢  ¥" + outMoney+ "  余額: ¥" + liveMoney);
        } else {
             System.out.println("線程名稱:" + Thread.currentThread().getName() + "取錢失敗,余額不足");
        }
        threadState = false;
        this.notify();
        System.out.println("線程名稱:" + Thread.currentThread().getName() + "被喚醒  " + "threadState = " + threadState);
     }
}

?運行結果

線程名稱:#1---  琪琪取錢  ¥600.0  余額: ¥400.0
線程名稱:#1---被喚醒  threadState = false
線程名稱:#3---取錢失敗,余額不足
線程名稱:#3---被喚醒  threadState = false
線程名稱:#2---被等待  
線程名稱:#2---在wait后執(zhí)行了該方法
線程名稱:#2---  岳岳取錢  ¥400.0  余額: ¥0.0
線程名稱:#2---被喚醒  threadState = false

?為什么線程處于等待狀態(tài)時要用while 循環(huán)?
?因為當線程被等待后喚醒依然會執(zhí)行while的循環(huán)條件,但是if將不會執(zhí)行。

什么是條件對象?
?線程進入臨界區(qū),卻發(fā)現在某一條件滿足之后它才能執(zhí)行,需要使用一個條件對象來管理那些已經獲得了鎖但是卻不能做有用工作的線程。例如工廠生產產品和商店賣出產品,但是商店的庫存小于要購買的產品的數量,這時必須通過工廠再生產產品才能繼續(xù)支持購買操作。當工廠在執(zhí)行生產操作時,條件對象就用來管理商店賣商品的操作。

?創(chuàng)建一個與該鎖相關的條件對象:Condition newCondition()。
?condition 方法必須被命名為await,signal,signalAll,以便它們不被某些方法沖突。
?await():將該線程放到條件的等待集中,直到線程從等待集中移出或等待了指定時間后才能解除阻塞。
?signalAll():解除該條件的等待集中的所有線程的阻塞狀態(tài)。
?signal():從該條件的等待集中隨機的選擇一個線程,解除其阻塞狀態(tài)。
調用wait()/notifyAll()等價于調用condition.await()/condition.signalAll();

e.g.

//A_Producter 類
package test;
public class A_Producter {
    private String productName ;//生產者名稱
    private int shirtNum;//T恤生產件數
    public String getProductName() {
        return productName;
    }
    public void setProductName(String productName) {
        this.productName = productName;
    }
    public int getShirtNum() {
        return shirtNum;
    }
    public void setShirtNum(int shirtNum) {
        this.shirtNum = shirtNum;
    }
    
    public A_Producter(String productName, int shirtNum) {
        this.productName = productName;
        this.shirtNum = shirtNum;
    }
    
}
//A_Consumer類
package test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class A_Consumer {
    private String consumerName;//購買人姓名
    private int shirtNum;//購買件數
    private A_Producter a_Producter;
    
    public String getConsumerName() {
        return consumerName;
    }
    public void setConsumerName(String consumerName) {
        this.consumerName = consumerName;
    }
    public int getShirtNum() {
        return shirtNum;
    }
    public void setShirtNum(int shirtNum) {
        this.shirtNum = shirtNum;
    }
    public A_Consumer(A_Producter a_Producter) {
        this.a_Producter = a_Producter;
    }

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();//創(chuàng)建一個與該鎖相關的條件對象
    int overplusShirt;
    public void consumer(String consumerName , int shirtNum) {
        lock.lock();//鎖定
        try{
        //如果購買的T恤總量大于生產的T恤或者大于剩余的T恤總量,則進行await加入等待集
             while( a_Producter.getShirtNum() < shirtNum) {
                    try {
                        condition.await();
                        System.out.println("調用await方法");
                    } catch (InterruptedException e) {}
                }
            if(a_Producter.getShirtNum() >= shirtNum){
                overplusShirt = a_Producter.getShirtNum() - shirtNum;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a_Producter.setShirtNum(overplusShirt);
                System.out.println(consumerName +"購買了 "+ shirtNum + " 件,剩余T恤 "+ overplusShirt +" 件");
            }
        }finally {
            lock.unlock();//解鎖
        }
        
    }
    
    //生產T恤
    public void product(String productName ,int producttNum) {
        lock.lock();//鎖定
        try{
                if( producttNum > 0) {
                    overplusShirt = overplusShirt+producttNum;
                    a_Producter.setShirtNum(overplusShirt);
                    System.out.println(productName + "生產了 "+ producttNum +"件T恤,剩余 "+ overplusShirt + " 件T恤");
                    condition.signalAll();
                }
            
        }finally {
            lock.unlock();//解鎖
        }
        
    }
    
}
//
package test;


public class A_ConditionObjTest {

    public static void main(String[] args) {
        
        A_Producter a_Producter = new A_Producter("丫妹兒", 15);
        A_Consumer a_Consumer = new A_Consumer(a_Producter);
        
        buyShirt qiqi = new buyShirt("琪琪", 5,a_Consumer);
        buyShirt yueyue = new buyShirt("岳岳", 4,a_Consumer);
        buyShirt qinqin = new buyShirt("琴琴", 7, a_Consumer);
        buyShirt fengfeng = new buyShirt("鳳鳳", 11, a_Consumer);
        
        new Thread(qiqi).start();//購買T恤
        new Thread(yueyue).start();
        new Thread(qinqin).start();
        new Thread(fengfeng).start();
        
        //生產T恤
        new Thread(new Runnable() {
            public void run() {
                a_Consumer.product("睿睿", 10);
            }
        }).start();
        
        new Thread(new Runnable() {
            public void run() {
                a_Consumer.product("康康", 20);
            }
        }).start();
        
    }
    
static class buyShirt implements Runnable{
    private String consumerName;
    private int shirtNum;
    private A_Consumer a_Consumer;


    public buyShirt(String consumerName, int shirtNum, A_Consumer a_Consumer) {
        super();
        this.consumerName = consumerName;
        this.shirtNum = shirtNum;
        this.a_Consumer = a_Consumer;
    }

    @Override
    public void run() {
        a_Consumer.consumer(consumerName, shirtNum);
    } 
   }    
}

運行結果

琪琪購買了 5 件,剩余T恤 10 件
岳岳購買了 4 件,剩余T恤 6 件
睿睿生產了 10件T恤,剩余 16 件T恤
康康生產了 20件T恤,剩余 36 件T恤
調用await方法
琴琴購買了 7 件,剩余T恤 29 件
調用await方法
鳳鳳購買了 11 件,剩余T恤 18 件

?一個鎖對象可以擁有一個或者多個相關的條件對象,可以通過newCondition方法來獲取一個條件對象。

通常,對await的調用應該在如下形式的循環(huán)體中

while(!(ok to proceed){
condition.await();
}

鎖對象

?每個對象都僅有一個鎖,如果兩個線程訪問不同的對象,那么每一個線程將獲得不同的鎖對象,因此兩個線程都不會發(fā)生阻塞。如果兩個線程訪問同一個對象,那么鎖以串行的方式提供服務。線程一旦獲取了該對象的鎖,其他訪問該對象的線程則無法再訪問該對象的其他非同步方法。

?使用鎖時,一定要注意標記為全局變量,如果標記為局部變量,每個線程再使用的時候都會創(chuàng)建一個新的鎖對象,這樣加鎖就沒有任何意義了。

鎖分為對象鎖、可重入鎖,公平鎖,讀寫鎖等。

可重鎖 ReentrantLock
?可重入鎖在執(zhí)行對象中所有同步方法不用再次獲得鎖。

公平鎖
?ReentrantLock(boolean fair) 構建一個帶有公平策略的鎖。一個公平鎖偏愛等待時間最長的線程,但這一公平的保證大大降低性能,所以在默認的情況下,鎖沒有被限制為公平。即使使用公平鎖,也無法保證線程調度是公平的。

讀寫鎖 ReentrantReadWriteLock
?讀寫鎖把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。一個讀寫鎖同時只能有一個寫者或多個讀者,但不能同時既有讀者又有寫者。讀寫鎖適用于多個線程從一個數據結構中讀取數據而很少修改其中數據的環(huán)境。

ReentrantReadWriteLock 主要有兩種方法:
?Lock readLock():得到一個可以被多個讀操作共用的讀鎖,但會排斥所有的寫操作。
?Lock writeLock():得到一個寫鎖,排斥所有其他的讀操作和寫操作。

讀寫操作必要步驟:

//構建ReentrantReadWriteLock 對象
    private ReentrantReadWriteLock writeAndread = new ReentrantReadWriteLock();
//獲取讀鎖和寫鎖
    private Lock readLock = writeAndread.readLock();
    private Lock writeLock = writeAndread.writeLock();

Lock接口中常用的方法有:
?1. lock():獲得鎖,如果鎖同時被另一個線程擁有,則發(fā)生阻塞。
?2. unlock():釋放這個鎖。
?3. tryLock():嘗試獲得鎖而沒有發(fā)生阻塞,則返回true。
?4. trylock(long time, TimeUnit unit):嘗試獲得鎖,在給定時間內不會發(fā)生阻塞,返回true。
?5. lockInterruptibly():獲得鎖,但是會不確定地發(fā)生阻塞。如果線程被中斷,則拋出InterruptedException異常。

    private Lock lock = new ReentrantLock();//可重入鎖
    public void outMoneySy(double outMoney,String name) {
        if(lock.tryLock()) {//嘗試獲取鎖,也可以使用trylock(long time, TimeUnit unit)來嘗試獲取鎖,如果超出給定時間還得不到鎖,則返回false
            lock.lock();
            try {
                double liveMoney = allMoney - outMoney;
                if (liveMoney >= 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    allMoney = liveMoney;
                    System.out.println("線程名稱:" + Thread.currentThread().getName() + "  " + name + "取錢  ¥" + outMoney+ "  余額: ¥" + liveMoney);
                } else {
                    System.out.println("取錢失敗,余額不足");
                }
            } finally {
                lock.unlock();
                System.out.println("線程名稱:" + Thread.currentThread().getName() + "釋放鎖  ");
            }
        }else {
            System.out.println("線程名稱:" + Thread.currentThread().getName() + "正在占用鎖  ");
        }
    }   

?必須把解鎖操作放在finally語句之內,如果在臨界區(qū)的代碼拋出異常,鎖必須被釋放,否則,其他線程將永遠被阻塞。線程每一次調用lock()都必須要調用unlock()來解鎖。
?因為Java中每一個對象都有一個內部鎖,所以

public synchronized void methodName(){
   代碼操作;
}
等價于
  public void methodName() {
        this.鎖對象.lock();//鎖定
        try {
         代碼操作;
        } finally {
            this.鎖對象.unlock();//解鎖
        }
    } 

死鎖

?什么是死鎖?死鎖是怎么形成的?
?當兩個線程互相等待對方釋放同步監(jiān)視器時就會發(fā)生死鎖。一旦出現死鎖,整個程序既不會發(fā)生任何異常,也不會給出任何提示,只是所有線程處于堵塞狀態(tài),無法繼續(xù)。如果同步中嵌套其他同步,程序出現無限等待,也可能會產生死鎖。系統(tǒng)資源的競爭和進程請求和釋放資源的順序不得當將會形成死鎖。

synchronzied(A鎖){
      synchronized(B鎖){
            代碼操作;
  }
}

e.g.

package test;

public class SynchronizedForLock {
     static Object lock1 = new Object() ;
     static Object lock2 = new Object(); 

    public static void main(String[] args) throws InterruptedException {
        OutMoney out1 = new OutMoney();
        OutMoney out2 = new OutMoney();
        out1.flag = 1;
        out2.flag = 0;
        new Thread(out1).start();
        new Thread(out2).start();
    }
    static class OutMoney implements Runnable {
        public int flag = 1;  
        public OutMoney() {}
        @Override
        public void run() {
             System.out.println("flag=" + flag);    
                if (flag == 1) {    
                    synchronized (lock1) {    
                        try {    
                            Thread.sleep(500);    
                        } catch (Exception e) {    
                            e.printStackTrace();    
                        }    
                        synchronized (lock2) {    
                            System.out.println("1");    
                        }    
                    }    
                }    
                if (flag == 0) {    
                    synchronized (lock2) {    
                        try {    
                            Thread.sleep(500);    
                        } catch (Exception e) {    
                            e.printStackTrace();    
                        }    
                        synchronized (lock1) {    
                            System.out.println("0");    
                        }    
                    }    
            }       
        }
    }
}

?程序運行結果

flag=0
flag=1

?
由運行結果看出,flag等于1時,同步obj1休眠了500毫秒,obj1被鎖定了,當flag等于0 時,同步obj2休眠了500毫秒,obj2被鎖定了.當obj1休眠結束后,需要鎖定obj2才能執(zhí)行,但是obj2已經被鎖定而未解鎖。當obj2休眠結束后,需要鎖定obj1才能執(zhí)行,但是obj1已經被鎖定。此時,obj1,obj2相互等待對方解鎖,從而出現死鎖現象。如果使用debug的方式運行程序,運行的結果可能和使用一般方式運行的結果不相同,那是因為使用debug方式運行時候,所打的斷點會影響程序的運行。(個人理解打斷點相當于使線程進行了休眠,從而產生不同的運行結果)

?如何避免死鎖的產生?
??1 . 在線程獲取鎖的時候給鎖加上一個時限,如果超過該時限就放棄對鎖的請求,并且釋放自己占有的鎖??梢允褂肔ock類中的tryLock方法來嘗試獲得鎖,如果沒有發(fā)生阻塞則返回true;
??2 . 對死鎖進行檢測,如果檢測出死鎖,則釋放所有的鎖,并且中斷線程或者回退。
??3 .給鎖加上順序,讓線程按照順序來獲得鎖。盡量減少嵌套。

synchronized的缺陷:當某個線程進入同步方法獲得對象鎖,那么其他線程訪問這里對象的同步方法時,必須等待或者阻塞,這對高并發(fā)的系統(tǒng)是致命的,這很容易導致系統(tǒng)的崩潰。如果某個線程在同步方法里面發(fā)生了死循環(huán),那么它就永遠不會釋放這個對象鎖,那么其他線程就要永遠的等待。這是一個致命的問題。

?Lock 與Synchronized的區(qū)別

類型 Synchronized Lock
存在層次 synchronized 是java中的關鍵字,在jvm層實現 Lock是一個接口, java.util.concurrent.locks
鎖的釋放 獲取鎖的線程執(zhí)行完同步代碼釋鎖;線程發(fā)生異常,會自動釋放線程占有的鎖 一般是在try{}finally{}結構中,解鎖操作必須放在finally語句之中,不然容易發(fā)生死鎖
鎖的獲取 如果A線程獲得了鎖,則B線程會等待;如果A線程被阻塞了,則B線程將會永遠等待 如果使用Lock.lock()方法的話,需要進行解鎖Lock.unlock()操作;如果使用tryLock()方法來嘗試獲得鎖,那么其他線程將不用等待,可以先去執(zhí)行其他的方法
鎖的狀態(tài) 無法獲取鎖的狀態(tài) 可以通過tryLock()方法來獲取鎖的狀態(tài),如果返回true,說明線程沒有發(fā)生阻塞
性能 少量同步 大量同步,性能要遠遠優(yōu)于synchronized(當并發(fā)量小的時候,性能和Synchronized區(qū)別不大)

線程池

什么是線程池?線程池有什么作用?
?就線程池的名稱來說,顧名思義,線程池就是一個存放多個線程的容器。一個線程池中包含了很多準備運行的空閑線程。將runnable對象交給線程池就會有一個線程調用run方法,當run方法退出時,線程不會死亡,而是在線程池中準備為下一個請求提供服務,省去了不斷創(chuàng)建和銷毀線程的過程,避免出現系統(tǒng)資源消耗過多的情況。創(chuàng)建和銷毀線程以及正在運行中的線程都會消耗非常多的系統(tǒng)資源從而導致系統(tǒng)資源不足。為了避免不斷系統(tǒng)資源不足的情況出現,使用線程池來存放多次使用額線程來使程序響應更快。

Callable與Future
?Callable與Runnable類似,但是有返回值:如Callable<Integer>,Callable接口只有一個call方法,用于運行一個將產生結果的任務。

Future用于保存異步計算的結果。
Future接口具有以下幾種方法:
?get(long time,TimeUnit unit):獲取結果,如果沒有結果可用,則阻塞直到超過制定的時間為止,如果不成功,第二個方法將會拋出TimeoutException異常。
?cancel(boolean mayinterruput):嘗試取消這一任務,如果任務已經開始,并且參數值為true,它就會被中斷,如果成功執(zhí)行了取消操作,則返回true。
?isCancelled():如果任務在完成之前被取消了,則返回true。
?isDone():如果任務結束,無論是正常結束,還是被中斷,取消或發(fā)生異常,都將返回true。

如何創(chuàng)建和使用線程池?
?執(zhí)行器(executor)中有很多靜態(tài)工廠的方法用來構建線程池

方法 描述
newCachedThreadPool 創(chuàng)建新線程,必要時線程會被保留60秒
newFixedThreadPool 該池包含固定數量的線程,空閑線程會被保留
newScheduledThreadPool 用于預定執(zhí)行而構建的固定線程池。
newSingleThreadExecutor 只有一個線程的池,該線程順序執(zhí)行沒有個提交的任務。
newSingleThreadScheduledExecutor 用于預定執(zhí)行而構建的單線程“池”。

使用線程池中的線程的步驟

  1. 創(chuàng)建線程池。
  2. 創(chuàng)建Runnable接口對象或者Callable接口對象
  3. 提交Runnable接口對象或者Callable接口對象。調用submit方法提交。
  4. 關閉線程。ThreadPoolExecutor提供了兩個方法來關閉線程池,一個是shutdown() ,一個是shutdownNow()。
    shutdown():不會立即終止線程,當所有任務都完成之后,線程池中的線程死亡。
    shutdownNow():立即終止線程,取消尚未開始的所有任務并試圖中斷正在運行的線程。
    e.g.
    Callable接口對象
package threadPool;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadPoolTest {

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);//創(chuàng)建線程池對象,其中包含兩個線程對象,如果線程數超過預設線程則等待。
        
        ThreadPool threadPool = new ThreadPool();

        pool.submit(threadPool);
        
        Future<String> result = pool.submit(threadPool);//提交Callable接口對象
        try {
            String backResult = result.get();//獲取返回結果
            System.out.println("打印返回結果 : "+ backResult);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
        //關閉線程
        pool.shutdown();
    
    }

}

//創(chuàng)建一個Callable接口對象,其返回值為String
class ThreadPool implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("這是一個測試線程池");
        return "哈哈哈,這是Callable返回的結果";
    }
}

運行結果

這是一個測試線程池
這是一個測試線程池
打印返回結果 : 哈哈哈,這是Callable返回的結果

總結不太好,有錯請及時指出 ?。?!

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

相關閱讀更多精彩內容

  • 1.Thread線程 線程中start和run方法有什么區(qū)別?wait和sleep方法的不同?sleep() 、j...
    KD小帥閱讀 248評論 0 0
  • 寫在前面的話: 這篇博客是我從這里“轉載”的,為什么轉載兩個字加“”呢?因為這絕不是簡單的復制粘貼,我花了五六個小...
    SmartSean閱讀 4,935評論 12 45
  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 3,105評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態(tài)及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,591評論 1 15
  • 你是否有用以下詞說過你孩子,是在他做什么,說什么的時候讓你這樣說的。例如:在早上起床喊了兒子二十分鐘時,他還沒有從...
    蘆葦花開閱讀 327評論 2 0

友情鏈接更多精彩內容