1. 程式人生 > >Java基礎之線程

Java基礎之線程

ive customer 原因 div http 辦理 display 多線程 mic

一、線程的基本概念

  程序(program)是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。

  進程(process)是程序的一次執行過程,或是正在運行的一個程序。動態過程:有它自身的產生、存在和消亡的過程。 如:運行中的QQ,運行中的MP3播放器 程序是靜態的,進程是動態的

  線程(thread),進程可進一步細化為線程,是一個程序內部的一條執行路徑。 若一個程序可同一時間執行多個線程,就是支持多線程的。

  進程與多線程:

技術分享圖片

  什麽時候需要多線程:

  1、程序需要同時執行兩個或多個任務。

  2、程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。

  3、需要一些後臺運行的程序時。

註意:每個Java程序都有一個隱含的主線程: main 方法

二、線程的創建與運行

  Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread類來實現。

  Thread類的特性:

     每個線程都是通過某個特定Thread對象的run()方法來完成操作的,經常把run()方法的主體稱為線程體。

    通過該Thread對象的start()方法來調用這個線程。

  1、Thread類

  構造方法

  Thread():創建新的Thread對象

  Thread(String threadname):創建線程並指定線程實例名

  Thread(Runnable target):指定創建線程的目標對象,它實現了Runnable接口中的run方法

  Thread(Runnable target, String name):創建新的Thread對象

  2、創建線程的兩種方式

  (1)、繼承Thread類

   1)、 定義子類繼承Thread類。

   2) 、子類中重寫Thread類中的run方法。

   3) 、創建Thread子類對象,即創建了線程對象。

   4) 、調用線程對象start方法:啟動線程,調用run方法。

public class MyThread extends Thread {
    
public void run(){ for (int a = 1; a < 10; a++){ System.out.println(Thread.currentThread().getName() + a); } } }
public class Test_Thread {
    public static void main(String[] args) {
        for (int a = 1; a < 10; a++){
            System.out.println(Thread.currentThread().getName() + a);//主線程
        }

        MyThread myThread1 = new MyThread();//開啟第一個線程
        myThread1.start();

        MyThread myThread2 = new MyThread();//開啟第二個線程
        myThread2.start();

        MyThread myThread3 = new MyThread();//開啟第三個線程
        myThread3.start();
    }
}

.  (2)、實現Runnable接口

   1)、定義子類,實現Runnable接口。

   2)、子類中重寫Runnable接口中的run方法。

   3)、通過Thread類含參構造器創建線程對象。

   4)、將Runnable接口的子類對象作為實際參數傳遞給 Thread類的構造方法中。

   5)、調用Thread類的start方法:開啟線程,調用 Runnable子類接口的run方法。

public class MyThread implements Runnable {
    int a = 30;
    @Override
    public void run() {
        for (int i = 0; i < a; i++){
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
public class Test_Thread {
    public static void main(String[] args) {
        for (int a = 0; a < 10; a++){
            System.out.println(Thread.currentThread().getName()+a);//主線程
        }

        MyThread myThread = new MyThread();//創建線程對象

        Thread thread1 = new Thread(myThread);//開啟第一個線程
        thread1.start();

        Thread thread2 = new Thread(myThread);//開啟第二個線程
        thread2.start();

        Thread thread3 = new Thread(myThread);//開啟第三個線程
        thread3.start();
    }
}

  (3)、實現資源共享

public class Test_Thread {
    public static void main(String[] args) {
        for (int a = 0; a < 10; a++){
            System.out.println(Thread.currentThread().getName() + a);
        }
        MyThread myThread = new MyThread();
        
        Thread thread1 = new Thread(myThread);
        thread1.start();
        thread1.setPriority(Thread.MAX_PRIORITY);

        Thread thread2 = new Thread(myThread);
        thread2.start();

        Thread thread3 = new Thread(myThread);
        thread3.start();
        thread3.setPriority(Thread.MIN_PRIORITY);
    }
}
public class MyThread implements Runnable{
    int a = 10;
    @Override
    public void run() {
        while (true){
            if (a < 0){
                break;
            }
            System.out.println(Thread.currentThread().getName() + a--);
        }
    }
}

  3、兩種創建線程的區別與聯系

 (1)、繼承Thread: 線程代碼存放Thread子類run方法中。 實現Runnable:線程代碼存在接口的子類的run方法。

 (2)、實現方式創建線程的好處

   1)避免了單繼承的局限性

   2)多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。

  

  4、Thread類的有關方法

  (1)、第一類

   void start(): 啟動線程,並執行對象的run()方法

   run(): 線程在被調度時執行的操作 String

   getName(): 返回線程的名稱

   void setName(String name):設置該線程名稱

   static currentThread(): 返回當前線程

  (2)、第二類(線程的調度)

  調度策略:

   時間片:技術分享圖片

  搶占式:高優先級的線程搶占CPU

  Java的調度方法: 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略 對高優先級,使用優先調度的搶占式策略

  線程的優先級控制:

  MAX_PRIORITY(10);

  MIN _PRIORITY (1);

  NORM_PRIORITY (5);

  涉及的方法: getPriority() :返回線程優先值

  setPriority(int newPriority) :改變線程的優先級 線程創建時繼承父線程的優先級

  (3)、第三類

    static void yield():線程讓步 暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程 若隊列中沒有同優先級的線程,忽略此方法

     join() :當某個程序執行流中調用其他線程的 join() 方法時,調用線程將被阻塞,直到 join() 方法加入的 join 線程執行完為止 低優先級的線程也可以獲得執行

    static void sleep(long millis):(指定時間:毫秒) 令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重排隊。 拋出InterruptedException異常

    stop(): 強制線程生命期結束

    boolean isAlive():返回boolean,判斷線程是否還活著

  5、線程的分類

  Java中的線程分為兩類:一種是守護線程,一種是用戶線程。

  它們在幾乎每個方面都是相同的,唯一的區別是判斷JVM何時離開。

  守護線程是用來服務用戶線程的,通過在start()方法前調用thread.setDaemon(true)可以把一個用戶線程變成一個守護線程。 Java垃圾回收就是一個典型的守護線程。 若JVM中都是守護線程,當前JVM將退出。

  6、為什麽要使用多線程

  背景:只使用單個線程完成多個任務(調用多個方法),肯定比用多個線程來完成用的時間更短,為何仍需多線程呢?

   (1)多線程程序的優點: 提高應用程序的響應。對圖形化界面更有意義,可增強用戶體驗。

   (2)提高計算機系統CPU的利用率。

   (3)改善程序結構,將既長又復雜的進程分為多個線程,獨立運行,利於理解和修改。

  7、線程的生命周期

  要想實現多線程,必須在主線程中創建新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命周期中通常要經歷如下的五種狀態:

  新建: 當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態。

  就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件。

  運行:當就緒的線程被調度並獲得處理器資源時,便進入運行狀態, run()方法定義了線程的操作和功能。

  阻塞:在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態。

  死亡:線程完成了它的全部工作或線程被提前強制性地中止。

技術分享圖片

三、線程的同步

  多個線程執行的不確定性引起執行結果的不穩定 多個線程對賬本的共享,會造成操作的不完整性,會破壞數據。

  舉個例子:

  (1)、你的賬戶上有3000元。有一個存折和一張卡對應的是這一個賬戶。

  (2)、你去銀行櫃臺取2000元,存折顯示你賬戶有3000元,櫃臺員工正在給你辦理。但是同時你的老婆在取款機上也取2000元,賬戶也顯示有3000元。這樣你們就取出4000元,這是很明顯的錯誤。

  (3)、解決辦法就是,在你取錢的同時,你的賬戶是不允許有其他人取錢的,你取過錢,你的賬戶的錢減去2000元後,才能夠其他取錢操作(資源獨占)。

  線程安全產生的原因:

  當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數據的錯誤。

  解決辦法:

  對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。

package main.dyh.test_two;

class Ticket implements Runnable{
    private static int tick = 100;
    public void run(){
        while(true){
            if(tick>0){
                System.out.println(Thread.currentThread().getName()+"售出車票,tick號為:"+ tick--);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                break;
            }
        }
    }
}

class  TicketDemo{
    public static void main(String[] args) {
        Ticket t = new Ticket();
        
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1窗口");
        t2.setName("t2窗口");
        t3.setName("t3窗口");

        t1.start();
        t2.start();
        t3.start();
    }
}

技術分享圖片

  安全問題的解決:

  Synchronized的使用方法:

    (1)、synchronized (對象){

       // 需要被同步的代碼;

     }

   即有synchronized關鍵字修飾的方法。
 由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
 synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類。

  synchronized還可以放在方法聲明中,表示整個方法 為同步方法。 例如:

    (2)、public synchronized void show (String name){

            ….

     }

  即有synchronized關鍵字修飾的語句塊。
 被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步

   註:同步是一種高開銷的操作,因此應該盡量減少同步的內容。
 通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。

  互斥鎖:

    在Java語言中,引入了對象互斥鎖的概念,來保證共享數據操作的完整性。

    每個對象都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。

    關鍵字synchronized 來與對象的互斥鎖聯系。當某個對象用synchronized修飾時,表明該對象在任一時刻只能由一個線程訪問。

    同步的局限性:導致程序的執行效率要降低 同步方法(非靜態的)的鎖為this。

    同步方法(靜態的)的鎖為當前類本身。

package main.dyh.test_two;

class Ticket implements Runnable{
    private static int tick = 100;
    public void run(){
        while(true){
            synchronized (this){
                if(tick>0){
                    System.out.println(Thread.currentThread().getName()+"售出車票,tick號為:"+ tick--);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else
                    break;
            }
        }
    }
}

class  TicketDemo{
    public static void main(String[] args) {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1窗口");
        t2.setName("t2窗口");
        t3.setName("t3窗口");

        t1.start();
        t2.start();
        t3.start();
    }
}

單例模式之懶漢模式:

技術分享圖片
class Singleton {
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance==null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance=new Singleton();
                }    }    }
        return instance;
    }     }
public class TestSingleton{
    public static void main(String[] args){
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        System.out.println(s1==s2);
    }    }
View Code

四、線程死鎖

  死鎖:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖

  解決方法: 專門的算法、原則 盡量減少同步資源的定義

五、線程通信

  wait() 與 notify() 和 notifyAll()

  •   wait():令當前線程掛起並放棄CPU、同步資源,使別的線程可訪問並修改共享資源,而當前線程排隊等候再次對資源的訪問
  •   notify():喚醒正在排隊等待同步資源的線程中優先級最高者結束等待
  •   notifyAll ():喚醒正在排隊等待資源的所有線程結束等待
  •   Java.lang.Object提供的這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常

  (1)、wait()

  •   在當前線程中調用方法: 對象名.wait()
  •   使當前線程進入等待(某對象)狀態 ,直到另一線程對該對象發出 notify (或notifyAll) 為止。
  •    調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)
  •   調用此方法後,當前線程將釋放對象監控權 ,然後進入等待
  •   在當前線程被notify後,要重新獲得監控權,然後從斷點處繼續代碼的執行。

  (2)、notify()與notifyAll()

  •   在當前線程中調用方法: 對象名.notify()
  •   功能:喚醒等待該對象監控權的一個線程。
  •   調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)

  經典案例:生產者消費者問題

  生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。

   這裏可能出現兩個問題:

  生產者比消費者快時,消費者會漏掉一些數據沒有取到。

  消費者比生產者快時,消費者會取相同的數據。

package main.dyh.test_two;

public class TestProduct {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Thread productorThread = new Thread(new Productor(clerk));
        Thread consumerThread = new Thread(new Consumer(clerk));
        productorThread.start();
        consumerThread.start();
    }
}

class Clerk{  //售貨員
    private int product = 0;
    public synchronized void addProduct(){
        if(product >= 20){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            product++;
            System.out.println("生產者生產了第"+product+"個產品");
            notifyAll();
        }
    }
    public synchronized void getProduct(){
        if(this.product <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println("消費者取走了第"+product+"個產品");
            product--;
            notifyAll();
        }
    }
}

class Productor implements Runnable{  //生產者
    Clerk clerk;
    public Productor(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        System.out.println("生產者開始生產產品");
        while(true){
            try {
                Thread.sleep((int)Math.random()*1000);
            } catch (InterruptedException e) {
            }
            clerk.addProduct();
        }
    }
}

class Consumer implements Runnable{  //消費者
    Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    public void run(){
        System.out.println("消費者開始取走產品");
        while(true){
            try {
                Thread.sleep((int)Math.random()*1000);
            } catch (InterruptedException e) {
            }
            clerk.getProduct();
        }
    }
}

Java基礎之線程