天天看點

Java學習筆記--線程和多線程線程池(簡單了解)

線程:

單核的cpu在一個時間片中隻能執行一個應用程式

各個程式其實在做cpu的資源真多戰而已 cpu做了快速的切換動作

疑問 :線程負責了代碼 的執行,我們之前沒有學過線程,為什麼代碼可以執行呢?

運作任何一個java程式,jvm在運作的時候都會建立一個main線程執行main方法中所有代碼。

一個java應用程式至少有幾個線程?

至少有兩個線程, 一個是主線程負責main方法代碼的執行,一個是垃圾回收器線程,負責了回收垃圾。

面試題

1)線程和程序有什麼差別?

一個程序是一個獨立(self contained)的運作環境,它可以被看作一個程式或者一個應用。而線程是在程序中執行的一個任務。線程是程序的子集,一個程序可以有很多線程,每條線程并行執行不同的任務。不同的程序使用不同的記憶體空間,而所有的線程共享一片相同的記憶體空間。别把它和棧記憶體搞混,每個線程都擁有單獨的棧記憶體用來存儲本地資料。

2)如何在Java中實作線程?

建立線程有兩種方式:

一、繼承 Thread 類,擴充線程。

二、實作 Runnable 接口。

3)Thread 類中的 start() 和 run() 方法有什麼差別?

調用 start() 方法才會啟動新線程;如果直接調用 Thread 的 run() 方法,它的行為就會和普通的方法一樣;為了在新的線程中執行我們的代碼,必須使用 Thread.start() 方法。

多線程的好處:

1. 解決了一個程序能同時執行多個任務的問題。

2. 提高了資源的使用率。

多線程 的弊端:

1. 增加cpu的負擔。

2. 降低了一個程序中線程的執行機率。

3. 引發了線程安全 問題。

4. 出現了死鎖現象。

如何建立多線程:

建立線程的方式:

    方式一:
        1. 自定義一個類繼承Thread類。
        2. 重寫Thread類的run方法 , 把自定義線程的任務代碼寫在run方法中
            疑問: 重寫run方法的目的是什麼?  
           每個線程都有自己的任務代碼,jvm建立的主線程的任務代碼就是main方法中的所有代碼,  
            自定義線程的任務代碼就寫在run方法中,自定義線程負責了run方法中代碼。   
        3. 建立Thread的子類對象,并且調用start方法開啟線程。

        注意: 一個線程一旦開啟,那麼線程就會執行run方法中的代碼,run方法千萬不能直接調用,  
        直接調用run方法就相當調用了一個普通的方法而已 并沒有開啟新的線程。
           

/*

需求: 模拟QQ視訊與聊天同時在執行。

*/

class TalkThread extends Thread{

    @Override
    public void run() {
        while(true){
            System.out.println("hi,你好!開視訊呗...");
        }
    }
}


class VideoThread extends Thread{

    @Override
    public void run() {
        while(true){
            System.out.println("視訊視訊....");
        }
    }
}

public class Demo2 {

    public static void main(String[] args) {
        TalkThread talkThread = new TalkThread();
        talkThread.start();
        VideoThread videoThread = new VideoThread();
        videoThread.start();    
    }
}           

還可以使用 匿名内部類建立多線程:

public class Demo4_Thread {

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

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

}           

線程常用的方法:

Thread(String name) 初始化線程的名字

setName(String name) 設定線程對象名

getName() 傳回線程的名字

重要 sleep() 線程睡眠指定的毫秒數。 靜态的方法, 哪個線程執行了sleep方法代碼那麼就是哪個線程睡眠。

重要 currentThread() 傳回目前的線程對象,該方法是一個靜态的方法, 注意: 那個線程執行了currentThread()代碼就傳回那個線程 的對象。

一般是Thread.currentThread()取得目前的線程

getPriority() 傳回目前線程對象的優先級 預設線程的優先級是5

s**etPriority(int newPriority)** 設定線程的優先級 雖然設定了線程的優先級,但是具體的實作取決于底層的作業系統的實作(最大的優先級是10 ,最小的1 , 預設是5)。

*/

多線程加鎖:

這裡隻介紹synchronized 還有其他鎖 可以參考這個部落客的

https://juejin.im/post/5a43ad786fb9a0450909cb5f

package cn.itcast.thread;

需求: 模拟3個視窗同時在售50張 票 。

問題1 :為什麼50張票被賣出了150次?

出現 的原因: 因為num是非靜态的,非靜态的成員變量資料是在每個對象中都會維護一份資料的,三個線程對象就會有三份。

解決方案:把num票數共享出來給三個線程對象使用。使用static修飾。

問題2: 出現了線程安全問題 ?

線程 安全問題的解決方案:sun提供了線程同步機制讓我們解決這類問題的。

java線程同步機制的方式:

    方式一:同步代碼塊

        同步代碼塊的格式:

            synchronized(鎖對象){
                需要被同步的代碼...
            }
           

同步代碼塊要注意事項:

1. 任意的一個對象都可以做為鎖對象。

2. 在同步代碼塊中調用了sleep方法并不是釋放鎖對象的。

3. 隻有真正存線上程安全問題的時候才使用同步代碼塊,否則會降低效率的。

4. 多線程操作的鎖 對象必須 是唯一共享 的。否則無效。

需求: 一個銀行賬戶5000塊,兩夫妻一個拿着 存折,一個拿着卡,開始取錢比賽,每次隻能取一千塊,要求不準出現線程安全問題。

方式二:同步函數
           

出現線程安全問題的根本原因:

1. 存在兩個或者兩個以上 的線程對象,而且線程之間共享着一個資源。

2. 有多個語句操作了共享資源。

實作Runnable接口比繼承Thread類所具有的優勢:

1):适合多個相同的程式代碼的線程去處理同一個資源

2):可以避免java中的單繼承的限制

3):增加程式的健壯性,代碼可以被多個線程共享,代碼和資料獨立

4):線程池隻能放入實作Runable或callable類線程,不能直接放入繼承Thread的類

提醒一下大家:main方法其實也是一個線程。在java中是以的線程都是同時啟動的,至于什麼時候,哪個先執行,完全看誰先得到CPU的資源。

在java中,每次程式運作至少啟動2個線程。一個是main線程,一個是垃圾收集線程。因為每當使用java指令執行一個類的時候,實際上都會啟動一個JVM,每一個jVM實際在就是在作業系統中啟動了一個程序。

class SaleTicket extends Thread{


 static int num = 50;//票數  非靜态的成員變量,非靜态的成員變量資料是在每個對象中都會維護一份資料的。

 static Object o = new Object();

 public SaleTicket(String name) {
    super(name);
}

@Override
public void run() {
    while(true){
        //同步代碼塊
        synchronized ("鎖") {                
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"售出了第"+num+"号票");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
            }else{
                System.out.println("售罄了..");
                break;
            }
        }

    }
}       
} 


public class Demo4 {

public static void main(String[] args) {
    //建立三個線程對象,模拟三個視窗
    SaleTicket thread1 = new SaleTicket("視窗1");
    SaleTicket thread2 = new SaleTicket("視窗2");
    SaleTicket thread3 = new SaleTicket("視窗3");
    //開啟線程售票
    thread1.start();
    thread2.start();
    thread3.start();    
    }   
}
           

線程的建立還有一個方法除了 繼承線程 然後代碼放入run之外

還可以繼承Runable

class zz implements Runnable{

@Override
public void run() {
    for (int i = 0; i <10; i++) {
        System.out.println("nei");
    }
}   
}

zz qq=new zz();

    Thread aa=new Thread(qq);
    aa.start();
           

鎖的話也可以有兩種 一種是使用同步代碼塊

格式 在run裡面 synchronized(鎖對象){需要同步的代碼}

同步函數 使用synchronized來修飾一個函數

同步函數要注意的事項 :

1. 如果是一個非靜态的同步函數的鎖 對象是this對象,如果是靜态的同步函數的鎖 對象是目前函數所屬的類的位元組碼檔案(class對象)。

2. 同步函數的鎖對象是固定的,不能由你來指定 的。

推薦使用: 同步代碼塊。
    原因:
        1. 同步代碼塊的鎖對象可以由我們随意指定,友善控制。同步函數的鎖對象是固定 的,不能由我們來指定。
        2. 同步代碼塊可以很友善控制需要被同步代碼的範圍,同步函數必須是整個函數 的所有代碼都被同步了。

//靜态的函數---->函數所屬 的類的位元組碼檔案對象--->BankThread.class  唯一的。
public static synchronized  void getMoney(){

}
           

當class被加載倒記憶體的時候 會建立一個class對象 把内容都存到這個對象裡面去 唯一

舉個例子 用繼承接口還有 同步函數來實作多線程

package day13;
class zz implements Runnable{

@Override
public void run() {
    zz();
}   
public synchronized void zz() {
    for (int i = 0; i <10; i++) {
        System.out.println("nei"+Thread.currentThread().getName());
    }
}
}
public class new1 {
public static void main(String[] args) throws InterruptedException   {
    zz qq1=new zz();
    zz qq2=new zz();
    Thread aa=new Thread(qq1);
    Thread bb=new Thread(qq2);
    aa.start();
    bb.start();

}

}
           

方式二:

1. 自定義一個類實作Runnable接口。

2. 實作Runnable接口 的run方法,把自定義線程的任務定義在run方法上。

3. 建立Runnable實作類對象。

4. 建立Thread類 的對象,并且把Runnable實作類的對象作為實參傳遞。

5. 調用Thread對象 的start方法開啟一個線程。

問題1: 請問Runnable實作類的對象是線程對象嗎?

Runnable實作類的對象并 不是一個線程對象,隻不過是實作了Runnable接口 的對象而已。

隻有是Thread或者是Thread的子類才是線程 對象。

問題2: 為什麼要把Runnable實作類的對象作為實參傳遞給Thread對象呢?作用是什麼?

作用就是把Runnable實作類的對象的run方法作為了線程的任務代碼去執行了。

推薦使用: 第二種。 實作Runable接口的。

原因: 因為java單繼承 ,多實作的。

/*

Thread類 的run方法

*  @Override
    public void run() {
        if (target != null) {
            target.run();  //就相當于Runnable實作類的對象的run方法作為了Thread對象的任務代碼了。
        }
    }
*/
           

Thread.currentThread()擷取目前正在執行的線程

System.out.println(Thread.currentThread().getName() + “…bb”);

禮讓線程:

class MyThread extends Thread {
    public void run() {
        for(int i = 1; i <= 1000; i++) {
            if(i % 10 == 0) {
                Thread.yield();                     //讓出CPU
            }
            System.out.println(getName() + "..." + i);
        }
    }
}           

死鎖問題; 相當于 一個人拿空調闆 一個人拿電池 隻有同時拿到時才可以進行下一步

舉例:

java中同步機制解決了線程安全問題,但是也同時引發死鎖現象。

死鎖現象:

死鎖現象出現 的根本原因:

1. 存在兩個或者兩個以上的線程。

2. 存在兩個或者兩個以上的共享資源。

死鎖現象的解決方案: 沒有方案。隻能盡量避免發生而已。

class DeadLock extends Thread{

public DeadLock(String name){
    super(name);
}


public void run() {
    if("張三".equals(Thread.currentThread().getName())){
        synchronized ("遙控器") {
            System.out.println("張三拿到了遙控器,準備 去拿電池!!");
            synchronized ("電池") {
                System.out.println("張三拿到了遙控器與電池了,開着空調爽歪歪的吹着...");
            }
        }
    }else if("狗娃".equals(Thread.currentThread().getName())){
        synchronized ("電池") { 
            System.out.println("狗娃拿到了電池,準備去拿遙控器!!");
            synchronized ("遙控器") {
                System.out.println("狗娃拿到了遙控器與電池了,開着空調爽歪歪的吹着...");
            }
        }

    }   
}


}

public class Demo2 {

public static void main(String[] args) {
    DeadLock thread1 = new DeadLock("張三");
    DeadLock thread2 = new DeadLock("狗娃");
    //開啟線程
    thread1.start();
    thread2.start();


}

}

如何避免死鎖  舉個例子:
public void run() {
    // TODO Auto-generated method stub
        synchronized("zzz") {
            synchronized("空調版") {
                System.out.println(name+"拿到空調版");
                synchronized("xx") {
                    System.out.println(name+"我拿到xx");
                }
            }   
        }   
    }
}
           

避免死鎖就是保證這兩個東西滿足的前提是必須先滿足其他一種情況

多線程總結 用接口Runnable來實作多線程 因為這樣還可以繼承其他類

用同步代碼塊來實作鎖 盡量避免死鎖

目前線程用Thread.currentThread()來表示

注意

public void run() {
        /*System.out.println("this:"+ this);
        System.out.println("目前線程:"+ Thread.currentThread());*/
        for(int i = 0 ; i < 100 ; i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }           

如果是Runnable來實作的話

this指的是這個接口實作類 而 Thread.currentThread指的是Thread那個類

還是貼一下 lock鎖的代碼吧 回顧的時候可以一眼知道

package com.heima.thread2;

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

public class Demo3_ReentrantLock {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Printer3 p = new Printer3();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print3();
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}

class Printer3 {
    private ReentrantLock r = new ReentrantLock();
    private Condition c1 = r.newCondition();
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();

    private int flag = 1;
    public void print1() throws InterruptedException {                          
        r.lock();                               //擷取鎖
            if(flag != 1) {
                c1.await();
            }
            System.out.print("我");
            System.out.print("是");
            System.out.print("線程");
            System.out.print("1");
            System.out.print("\r\n");
            flag = 2;
            //this.notify();                        //随機喚醒單個等待的線程
            c2.signal();
        r.unlock();                             //釋放鎖
    }

    public void print2() throws InterruptedException {
        r.lock();
            if(flag != 2) {
                c2.await();
            }
            System.out.print("我");
            System.out.print("是");
            System.out.print("線程");
            System.out.print("2");
            System.out.print("\r\n");
            flag = 3;
            //this.notify();
            c3.signal();
        r.unlock();
    }

    public void print3() throws InterruptedException {
        r.lock();
            if(flag != 3) {
                c3.await();
            }
            System.out.print("我");
            System.out.print("是");
            System.out.print("線程");
            System.out.print("3");
            System.out.print("\r\n");
            flag = 1;
            c1.signal();
        r.unlock();
    }
}
           

回過頭來又來看一遍ReentrantLock鎖:

了解更多了點:

第一:r.lock();和r.unlock();

還是拿上面這個栗子來說 一個類裡有三個方法 三個線程分别調用三個方法,都是搶占式的 那會不會當一個線程在執行的時候 另一個線程會搶斷這個線程呢?

答案:不會的

因為這個這個方法加了鎖lock

我原來以為多個線程調用同一段代碼的時候這個鎖才有用 後來知道了 範圍還可以更廣

因為 它是用代碼中 同一個 r (ReentrantLock鎖)來鎖定這段代碼的 注意是同一個

雖然r鎖定的代碼不一樣 但是 他們都是由一個鎖鎖住的 一個鎖隻能鎖一個

是以 當線程A執行的時候 是不會被線程B搶斷的

第二:c1.await(); 意思就是睡眠 睡眠什麼呢?

我了解的 睡眠這個線程的 目前位置 await()下面代碼不會被執行

同時釋放鎖

如果被喚醒了 還是從之前被鎖定的位置開始

————————————————————————————————————————————————————

線程通信:

線程通訊: 一個線程完成了自己的任務時,要通知另外一個線程去完成另外一個任務.

生産者與消費者

wait(): 等待 如果線程執行了wait方法,那麼該線程會進入等待的狀态,等待狀态下的線程必須要被其他線程調用notify方法才能喚醒。

notify(): 喚醒 喚醒線程池等待線程其中的一個。

notifyAll() : 喚醒線程池所有等待 線程。

wait與notify方法要注意的事項:

1. wait方法與notify方法是屬于Object對象 的。

2. wait方法與notify方法必須要在同步代碼塊或者是同步函數中才能 使用。

3. wait方法與notify方法必需要由鎖對象調用。

如果不加鎖 會出現了線程安全問題。 價格錯亂了…

//産品類
class Product{

String name;  //名字

double price;  //價格

boolean flag = false; //産品是否生産完畢的辨別,預設情況是沒有生産完成。

}

//生産者
class Producer extends Thread{

Product  p ;    //産品

public Producer(Product p) {
    this.p  = p ;
}



@Override
public void run() {
    int i = 0 ; 
    while(true){
     synchronized (p) {
        if(p.flag==false){
             if(i%2==0){
                 p.name = "蘋果";
                 p.price = 6.5;
             }else{
                 p.name="香蕉";
                 p.price = 2.0;
             }
             System.out.println("生産者生産出了:"+ p.name+" 價格是:"+ p.price);
             p.flag = true;
             i++;
             p.notifyAll(); //喚醒消費者去消費
        }else{
            //已經生産 完畢,等待消費者先去消費
            try {
                p.wait();   //生産者等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }   
  } 
}
}


//消費者
class Customer extends Thread{

Product p; 

public  Customer(Product p) {
    this.p = p;
}


@Override
public void run() {
    while(true){
        synchronized (p) {  
            if(p.flag==true){  //産品已經生産完畢
                System.out.println("消費者消費了"+p.name+" 價格:"+ p.price);
                p.flag = false; 
                p.notifyAll(); // 喚醒生産者去生産
            }else{
                //産品還沒有生産,應該 等待生産者先生産。
                try {
                    p.wait(); //消費者也等待了...
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }   
}
}

public class Demo5 {

public static void main(String[] args) {
    Product p = new Product();  //産品
    //建立生産對象
    Producer producer = new Producer(p);
    //建立消費者
    Customer customer = new Customer(p);
    //調用start方法開啟線程
    producer.start();
    customer.start();


}

}
           

注意 鎖是synchronized 等待是wait 喚醒是notify 對象得是鎖的那個對象

wait:告訴目前線程放棄執行權,并放棄螢幕(鎖)并進入阻塞狀态,直到其他線程持有獲得執行權,并持有了相同的螢幕(鎖)并調用notify為止。

notify:喚醒持有同一個螢幕(鎖)中調用wait的第一個線程,例如,餐館有空位置後,等候就餐最久的顧客最先入座。注意:被喚醒的線程是進入了可運作狀态。等待cpu執行權。

notifyAll:喚醒持有同一螢幕中調用wait的所有的線程。

如何解決生産者和消費者的問題?

可以通過設定一個标記,表示資料的(存儲空間的狀态)例如,當消費者讀取了(消費了一次)一次資料之後可以将标記改為false,當生産者生産了一個資料,将标記改為true。

,也就是隻有标記為true的時候,消費者才能取走資料,标記為false時候生産者才生産資料。

線程間通信其實就是多個線程在操作同一個資源,但操作動作不同,wait,notify(),notifyAll()都使用在同步中,因為要對持有螢幕(鎖)的線程操作,是以要使用在同步中,因為隻有同步才具有鎖。

為什麼這些方法定義在Object類中

因為這些方法在操作線程時,都必須要辨別他們所操作線程持有的鎖,隻有同一個鎖上的被等待線程,可以被統一鎖上notify喚醒,不可以對不同鎖中的線程進行喚醒,就是等待和喚醒必須是同一個鎖。而鎖由于可以使任意對象,是以可以被任意對象調用的方法定義在Object類中

wait() 和 sleep()有什麼差別?

wait():釋放資源,釋放鎖。是Object的方法

sleep():釋放資源,不釋放鎖。是Thread的方法

定義了notify為什麼還要定義notifyAll,因為隻用notify容易出現隻喚醒本方線程情況,導緻程式中的所有線程都在等待。

線程的停止:

1. 停止一個線程 我們一般都會通過一個變量去控制的。

2. 如果需要停止一個處于等待狀态下的線程,那麼我們需要通過變量配合notify方法或者interrupt()來使用。notify需要鎖對象 interrupt()不需要而且可以指定線程但是會抛出異常

調用線程對象的interrupt()時,sleep的線程會抛出InterruptedException異常,進而中斷循環,終止線程。

但是如果是IO如輸入這些阻塞,中斷的方法又不起作用了,還有就是對于沒有阻塞的線程,調用interrupt()是達不到終止線程的效果的。

對于IO阻塞,可以關閉IO通道

interrupt()的話會在中斷位置抛出異常 抛出異常是為了線程從阻塞狀态醒過來,并在結束線程前讓程式員有足夠的時間來進行中斷請求。

Thread.interrupt()方法不會中斷一個正在運作的線程。這一方法實際上完成的是,線上程受到阻塞時抛出一個中斷信号,這樣線程就得以退出阻塞的狀态。更确切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它将接收到一個中斷異常(InterruptedException),進而提早地終結被阻塞狀态。

是以,如果線程被上述幾種方法阻塞,正确的停止線程方式是設定共享變量,并調用interrupt()(注意變量應該先設定)。  
如果線程沒有被阻塞,這時調用interrupt()将不起作用;否則,線程就将得到異常(該線程必須事先預備好處理此狀況),  
接着逃離阻塞狀态。
           

A. 無論synchronized關鍵字加在方法上還是對象上,如果它作用的對象是非靜态的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜态方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。

B. 每個對象隻有一個鎖(lock)與之相關聯,誰拿到這個鎖誰就可以運作它所控制的那段代碼。

C. 實作同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,是以盡量避免無謂的同步控制。

如:

public synchronized void run() {}那麼鎖對象就是這個對象 
public synchronized static void xx(){}這個時候鎖對象是這個class檔案
舉個例子:
package day13;
class xx implements Runnable{
    public synchronized void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
public class new1 {
    public static void main(String[] args) throws InterruptedException   {
        xx zz=new xx();
        for (int i = 0; i < 4; i++) {   
            Thread aa=new Thread(zz);
            aa.setName("name"+i);
            aa.start();
        }
    }

}           

鎖是zz這個對象 是以不會發生混亂

wait要放在synchronized就是鎖的裡面 wait要對象才行 一般配合着都是wait鎖

wait是把目前線程放入鎖線程池中

notify是喚醒鎖中的任意一個線程

一、sleep和wait的主要差別:

1、這兩個方法來自不同的類分别是Thread和Object

2、最主要是sleep方法沒有釋放鎖,而 wait 方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。

3、wait,notify和notifyAll隻能在同步控制方法或者同步控制塊裡面使用,而sleep可以在任何地方使用(使用範圍)

4、sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

5、sleep(milliseconds)可以用時間指定來使他自動醒過來,如果時間不到你隻能調用interreput()來強行打斷;wait()可以用notify()直接喚起.

線程中斷

線程的停止:

2. 如果需要停止一個處于等待狀态下的線程,那麼我們需要通過變量配合notify方法或者interrupt()來使用。

package day13;
class zx implements Runnable{
    boolean flag=true;
    int i=0;
    public synchronized void run() {
        while(flag) {
            /*try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }*/
            System.out.println("内"+i);
        }

    }
}
public class new1 {
    public static void main(String[] args) throws InterruptedException   {
        zx xx=new zx();
        Thread aa=new Thread(xx);
        aa.start();
            for (int i = 0; i < 100; i++) {
                System.out.println("main"+i);
                if(i==30) {
                    xx.flag=false;   設定了變量控制
                    aa.interrupt();//把線程的等待狀态強制清除,被清除狀态的線程會接收到一個  
                    InterruptedException。 

                }
            }
    }

}           

但是取消了對象中try的注釋 那麼系統就無法停止

因為線程是wait狀态 要取消有兩種方法

一種是

/*synchronized (xx) {

xx.notify();

}*/

這樣子把該對象線程池中線程喚醒出來

第二種是:

xx.flag = false;

aa.interrupt(); //把線程的等待狀态強制清除,被清除狀态的線程會接收到一個InterruptedException。

可以指定喚醒哪個線程 而notify不行

注意:使用了 wait後就釋放了鎖其他線程可以進來 放入對應的鎖線程池中等待喚醒或者強制打斷

interrupt并不是直接打斷線程 而是告訴線程你該被取消了 給線程加上了辨別符true

如果這個時候有wait或者sleep等阻塞的時候那麼就會強制打斷線程(取消wait狀态) 辨別符再改為false

如果沒有wait等阻塞那麼隻是給線程加上true而已 不會打斷線程

但是注意: i0之類的阻塞不能用interrupt

而且一旦中斷就會抛出異常

記住 wait是object 一般都是這個同步鎖

interrupt是指定清除哪個線程的等待狀态

而notify是随機一個

守護線程 :

背景線程:就是隐藏起來一直在默默運作的線程,直到程序結束。

實作:

setDaemon(boolean on)

特點:

當所有的非背景線程結束時,程式也就終止了同時還會殺死程序中的所有背景線程,也就是說,隻要有非背景線程還在運作,程式就不會終止,執行main方法的主線程就是一個非背景線程。

必須在啟動線程之前(調用start方法之前)調用setDaemon(true)方法,才可以把該線程設定為背景線程。

一旦main()執行完畢,那麼程式就會終止,JVM也就退出了。

可以使用isDaemon() 測試該線程是否為背景線程(守護線程)。

守護線程(背景線程):在一個程序中如果隻剩下 了守護線程,那麼守護線程也會死亡。

需求: 模拟QQ下載下傳更新包。

一個線程預設都不是守護線程。

public class Demo7 extends Thread {

public Demo7(String name){
    super(name);
}

@Override
public void run() {
    for(int i = 1 ; i<=100 ; i++){
        System.out.println("更新包目前下載下傳"+i+"%");
        if(i==100){
            System.out.println("更新包下載下傳完畢,準備安裝..");
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) {
     Demo7 d = new Demo7("背景線程");
     d.setDaemon(true); //setDaemon() 設定線程是否為守護線程,true為守護線程, false為非守護線程。
    // System.out.println("是守護線程嗎?"+ d.isDaemon()); //判斷線程是否為守護線程。
     d.start();

     for(int i = 1 ; i<=100 ; i++){
         System.out.println(Thread.currentThread().getName()+":"+i);
     }

}
           

}

Timer:

線程安排在背景執行的任務 可執行一次或者多次重複

就比如手機鬧鈴 可定期重複響鈴

public static void main(String[] args) throws InterruptedException {
        Timer t = new Timer();
        //在指定時間安排指定任務
        //第一個參數,是安排的任務,第二個參數是執行的時間,第三個參數是過多長時間再重複執行
        t.schedule(new MyTimerTask(), new Date(188, 6, 1, 14, 22, 50),3000);    

        while(true) {
            Thread.sleep(1000);
            System.out.println(new Date());
        }
    }

}

class MyTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println("起床背英語單詞");
    }           

總結:

TimerTask是繼承runnable

寫個類繼承TimerTask即可

簡單線程池建立:

public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);//建立線程池
        pool.submit(new MyRunnable());              //将線程放進池子裡并執行
        pool.submit(new MyRunnable());

        pool.shutdown();                            //關閉線程池
    }           

多線程還有一種建立方法 :了解就行 可以有傳回值 可以抛出異常

class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1; i <= num; i++) {
            sum += i;
        }

        return sum;
    }

}
public class One {

    public static void main(String args[]) throws InterruptedException, ExecutionException {    
        ExecutorService pool = Executors.newFixedThreadPool(2);//建立線程池
        Future<Integer> f1 = pool.submit(new MyCallable(100));              //将線程放進池子裡并執行
        Future<Integer> f2 = pool.submit(new MyCallable(50));

        System.out.println(f1.get());
        System.out.println(f2.get());


    }
}           

join:

Thread的join方法

當A線程執行到了B線程Join方法時A就會等待,等B線程都執行完A才會執行,Join可以用來臨時加入線程執行

join方法。 加入

//老媽
class  Mon extends Thread{

public void run() {
    System.out.println("媽媽洗菜");
    System.out.println("媽媽切菜");
    System.out.println("媽媽準備炒菜,發現沒有醬油了..");
    //叫兒子去打醬油
    Son s= new Son();
    s.start();
    try {
        s.join();  //加入。 一個線程如果執行join語句,那麼就有新的線程加入,
        執行該語句的線程必須要讓步給新加入的線程先完成任務,然後才能繼續執行。
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("媽媽繼續炒菜");
    System.out.println("全家一起吃飯..");     
}
} 

class Son extends Thread{

@Override
public void run() {
    System.out.println("兒子下樓..");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("兒子一直往前走");
    System.out.println("兒子打完醬油了");
    System.out.println("上樓,把醬油給老媽");
}
}

public class Demo8 {

public static void main(String[] args) {
    Mon m = new Mon();
    m.start();



}
}
           

線程組:

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "...." + i);
        }
    }

}
    public static void main(String[] args) {
        //demo1();
        ThreadGroup tg = new ThreadGroup("我是一個新的線程組");      //建立新的線程組
        MyRunnable mr = new MyRunnable();                       //建立Runnable的子類對象

        Thread t1 = new Thread(tg, mr, "張三");                   //将線程t1放在組中
        Thread t2 = new Thread(tg, mr, "李四");                   //将線程t2放在組中

        System.out.println(t1.getThreadGroup().getName());      //擷取組名
        System.out.println(t2.getThreadGroup().getName());

        tg.setDaemon(true);
    }           

參考:

簡書1 簡書2 守護線程

  1)wait()、notify()和notifyAll()方法是本地方法,并且為final方法,無法被重寫。

  2)調用某個對象的wait()方法能讓目前線程阻塞,并且目前線程必須擁有此對象的monitor(即鎖)

  3)調用某個對象的notify()方法能夠喚醒一個正在等待這個對象的monitor的線程,如果有多個線程都在等待這個對象的monitor,則隻能喚醒其中一個線程;

  4)調用notifyAll()方法能夠喚醒所有正在等待這個對象的monitor的線程;

  有朋友可能會有疑問:為何這三個不是Thread類聲明中的方法,而是Object類中聲明的方法(當然由于Thread類繼承了Object類,是以Thread也可以調用者三個方法)?其實這個問題很簡單,由于每個對象都擁有monitor(即鎖),是以讓目前線程等待某個對象的鎖,當然應該通過這個對象來操作了。而不是用目前線程來操作,因為目前線程可能會等待多個線程的鎖,如果通過線程來操作,就非常複雜了。

  上面已經提到,如果調用某個對象的wait()方法,目前線程必須擁有這個對象的monitor(即鎖),是以調用wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。

  調用某個對象的wait()方法,相當于讓目前線程交出此對象的monitor,然後進入等待狀态,等待後續再次獲得此對象的鎖(Thread類中的sleep方法使目前線程暫停執行一段時間,進而讓其他線程有機會繼續執行,但它并不釋放對象鎖);

  notify()方法能夠喚醒一個正在等待該對象的monitor的線程,當有多個線程都在等待該對象的monitor的話,則隻能喚醒其中一個線程,具體喚醒哪個線程則不得而知。

  同樣地,調用某個對象的notify()方法,目前線程也必須擁有這個對象的monitor,是以調用notify()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。

  nofityAll()方法能夠喚醒所有正在等待該對象的monitor的線程,這一點與notify()方法是不同的。

  這裡要注意一點:notify()和notifyAll()方法隻是喚醒等待該對象的monitor的線程,并不決定哪個線程能夠擷取到monitor。

  舉個簡單的例子:假如有三個線程Thread1、Thread2和Thread3都在等待對象objectA的monitor,此時Thread4擁有對象objectA的monitor,當在Thread4中調用objectA.notify()方法之後,Thread1、Thread2和Thread3隻有一個能被喚醒。注意,被喚醒不等于立刻就擷取了objectA的monitor。假若在Thread4中調用objectA.notifyAll()方法,則Thread1、Thread2和Thread3三個線程都會被喚醒,至于哪個線程接下來能夠擷取到objectA的monitor就具體依賴于作業系統的排程了。

  上面尤其要注意一點,一個線程被喚醒不代表立即擷取了對象的monitor,隻有等調用完notify()或者notifyAll()并退出synchronized塊,釋放對象鎖後,其餘線程才可獲得鎖執行。

package fuxi;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import javax.swing.JButton;

public class One {
     public static Object object = new Object();
    public static void main(String[] args) {
           Thread1 thread1 = new Thread1();
            Thread2 thread2 = new Thread2();

            thread1.start();

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            thread2.start();
    }
     static class Thread1 extends Thread{
            @Override
            public void run() {
                synchronized (object) {
                    try {
                        System.out.println("1");
                        object.wait();

                    } catch (InterruptedException e) {
                    }
                    System.out.println("3");
                    System.out.println("線程"+Thread.currentThread().getName()+"擷取到了鎖");
                }
            }
        }

        static class Thread2 extends Thread{
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("2");
                    object.notify();
                    System.out.println("4");
                    System.out.println("線程"+Thread.currentThread().getName()+"調用了object.notify()");
                }
                System.out.println("線程"+Thread.currentThread().getName()+"釋放了鎖");
            }
        }
}
           

  運作結果是:

1
2
4
線程Thread-1調用了object.notify()
3
線程Thread-1釋放了鎖
線程Thread-0擷取到了鎖           

得到的結論是 如果一個線程A wait必須在鎖内 而且喚醒的話 線程B也要在鎖内 而且是等這個鎖内代碼執行完之後才開始再次競争 不一定被線程A搶到鎖 可能還是B搶到鎖 還有 如果A搶到鎖了 那麼就會繼續上次的執行 這個就是線程搶占CPU 一樣 被搶占的那個記錄自己運作位置 搶回來後 繼續那個位置執行

參考自:

http://www.cnblogs.com/dolphin0520/p/3920385.html