Java進程線程筆記

  

  什麼是並行和併發?

併發和並行是即類似又有區別:(微觀)java

並行:指兩個或多個事件在同一時刻發生; 強調的是時間點.程序員

併發:指兩個或多個事件在同一時間段內發生; 強調的是時間段.windows

  

  進程和線程的區別?

進程:有獨立的內存空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。 線程:堆空間是共享的,棧空間是獨立的,線程消耗的資源也比進程小,相互之間能夠影響的,又稱爲輕型進程或進程元。 由於一個進程中的多個線程是併發運行的,那麼從微觀角度上考慮也是有前後順序的,那麼哪一個線程執行徹底取決於CPU調度器,程序員是控制不了的。咱們能夠把多線程併發性看做是多個線程在瞬間搶CPU資源,誰搶到資源誰就運行,這也造就了多線程的隨機性。安全

 

  怎麼建立進程?

建立進程的方式有兩種,我以windows上的記事本爲例:網絡

package com.StadyJava.day14;

import java.io.IOException;

public class ProcessDemo {
    public static void main(String[] args) throws Exception {
        //方法1:使用Runtime
        Runtime runtime=Runtime.getRuntime();
        runtime.exec("notepad");

        //方法2:ProcessBuild
        ProcessBuilder processBuilder=new ProcessBuilder("notepad");
        processBuilder.start();
    }
}

運行代碼,此時會生成兩個記事本。多線程

我也嘗試過啓動其餘的程序,可是計算器不認識,只有notepad這種計算機自帶的才認識。併發

若是想要啓動其餘的程序,只能寫上絕對路徑app

ProcessBuilder processBuilder=new ProcessBuilder("E:\\shuyunquan\\TIM\\Bin\\TIM.exe");
processBuilder.start();

這樣我測試了,是能夠的,可是沒意思,這樣你寫了程序發佈了也搞怪不了,別人電腦上的路徑和你不同。。。ide

接下來說解線程。性能

 

 

下圖是線程的一些經常使用的方法 

 

建立進程的兩個方法已經知道了,接下來看看建立線程的兩個方法

建立線程方法1:使用繼承Thread類

package com.StadyJava.day14Thread;

import  java.lang.Thread;

class Music extends Thread{
    
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("聽音樂"+i);
        }
    }
}

public class MusicThread {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("打遊戲"+i);
            if (i == 10) {
                Music music=new Music();
                music.start();
            }
        }
    }
}

注意,繼承了Thread類以後要重寫run方法,並且在調用的時候,只能使用start方法,不能調用run方法,切記!

輸出的結果是在打遊戲10以後,下面的打遊戲和聽音樂都是隨機出現,由於主線程main和線程music在搶佔資源,誰搶到誰執行,因此輸出的結果是隨機的

(注意!我使用Idea輸出的結果是順序的,不是隨機的。我同窗和我同樣的代碼使用Eclipse結果是隨機的,我本身複製到Eclipse以後偶爾隨機,Idea是死都不隨機,這個我解決不了,但願之後知道緣由或者有人告訴我)

 

建立線程的方法2:實現Runnable接口

其實Thread類也是實現了Runnable接口的,因此這個方法我感受是本源??

package com.StadyJava.day14Thread;

import  java.lang.Runnable;

class MusicRun implements Runnable{

    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("聽音樂"+i);
        }
    }
}

public class MusicRunnable {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("打遊戲"+i);
            if (i == 10) {
                Runnable music=new MusicRun();
                Thread thread=new Thread(music);
                thread.start();
            }
        }
    }
}

沒什麼特別的,就是生成一個runnable的對象,而後Thread對象加載進這個Runnable對象,最後start方法調用一下就完事了。這個結果個人Idea依然是顯示的不符合個人指望的。

 

 

上面兩種建立線程的方式是最經常使用的方式,通常也就足夠了,下面介紹一下不怎麼經常使用的

建立線程的方法3:匿名內部類建立線程

匿名內部類的格式:new 接口(){}  應該是這樣的,待補充

其實匿名內部類建立線程仍是使用上面的兩種方式,只不過那個Music類我不須要去定義了,這就是匿名內部類,看代碼吧

package com.StadyJava.day14Thread;

import  java.lang.Runnable;

class MusicRun implements Runnable{

    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("聽音樂"+i);
        }
    }
}

public class MusicRunnable {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("打遊戲"+i);
            if (i == 10) {

                //匿名內部類的形式1,使用接口
              new Thread(new Runnable() {

                  public void run() {
                      for (int i = 0; i < 50; i++) {
                          System.out.println("聽音樂"+i);
                      }
                  }
              }).start();

              //匿名內部類的形式2,使用繼承類
                new Thread(){
                public void run() {
                    for (int i = 0; i < 50; i++) {
                        System.out.println("聽音樂"+i);
                    }
                }
            }.start();
            }
        }
    }
}

這回,輸出的結果總算是符合個人預期了,多是線程變成了3個,搶佔資源激烈了些。。。

 

學了兩種常見的建立線程的方法以後,他們之間有什麼區別呢?

Thread建立線程和Runnable建立線程的區別

 我寫個例子,吃蘋果大賽,3我的參加比賽,先使用繼承Thread類建立線程的方式,代碼以下:

package com.StadyJava.day14;


class Person extends java.lang.Thread{

    private int num=50;
    public Person(String name){
        super(name);
    }

    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num >0) {
                System.out.println(super.getName()+"吃了編號爲"+num--+"的蘋果");
            }

        }
    }
}

public class EatAppleThread {
    public static void main(String[] args) {
        //建立3我的,去參加吃蘋果大賽
        new Person("許嵩").start();
        new Person("林俊杰").start();
        new Person("蜀雲泉").start();

    }
}

 這個輸出的結果,就是許嵩,林俊杰,蜀雲泉每一個人都吃了50 個🍎。緣由是由於我new了三個對象,沒個對象都有num變量,他們之間互不干擾,以下圖所示:

 

 

這樣很很差,個人吃蘋果大賽是總共50個🍎,大家3我的來吃就完事了。咱們看看實現Runnable接口建立線程的方式是怎麼樣的,代碼以下:

package com.StadyJava.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num >0) {
                //返回當前線程的引用Thread.currentThread(),再獲取名字
                System.out.println(Thread.currentThread().getName()+"吃了編號爲"+num--+"的蘋果");
            }

        }
    }
}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //建立3我的,去參加吃蘋果大賽
        Apple apple=new Apple();
        new Thread(apple,"許嵩").start();
        new Thread(apple,"林俊杰").start();
        new Thread(apple,"蜀雲泉").start();

    }
}

 此次由於我傳入的都是apple這個對象,這一個對象有50個🍎,建立了3個線程,此次輸出的結果是OK的,緣由以下圖所示,3個線程共用了一個蘋果對象。

 

 

 從這個吃蘋果比賽的例子中能夠總結一下繼承Thread類建立線程的方式和實現Runnable接口建立線程的方式的區別:

繼承Thread類建立線程方式:

  1. Java中類是單繼承的,若是使用了繼承Thread類建立線程,那麼就 不能再有其餘父類了,這是一個限制
  2. 從操做上來講,繼承Thread類的方式更簡單,獲取線程名稱也簡單,直接getName就行了。操做簡單,這是優勢
  3. 從多線程共享資源的方面分析,繼承方式不行,直接3我的,每一個人50個蘋果,沒有實現共享,這是缺點

 

實現Runnable接口建立線程方式:

  1.  Java中類是能夠實現多接口的,因此實現Runnable接口建立線程,也能夠繼續的去實現其餘接口,也能夠去繼承類,設計優雅,這是優勢
  2. 從操做上分析,實現接口方式有點複雜,獲取線程名稱的時候,必須使用Thread.currentThread()來獲取當前線程的引用
  3. 從多線程共享資源的方面上,實現接口方式能夠作到共享資源,3我的去吃50個蘋果,共享資源。這是優勢

 

 

綜合上面的區別對比,咱們的這個比賽。看來只能使用實現Runnable接口建立線程的方式來實現了。推薦之後建立線程,都使用實現Runnable接口的方式。

 

 

 線程安全問題

 拿上面寫的實現接口建立線程的吃蘋果比賽爲例,這個是存在線程安全的,咱們能夠寫一個線程休眠來看看,這樣更容易觀察。

須要說明,Thread.sleep線程休眠,須要使用try catch來撲捉異常,不能使用throw拋出,由於Runnable接口裏面的run方法自己就沒有throw的寫法

package com.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num >0) {
                //線程休眠,必須使用try catch撲捉異常
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //返回當前線程的引用Thread.currentThread(),再獲取名字
                System.out.println(Thread.currentThread().getName()+"吃了編號爲"+num--+"的蘋果");
            }

        }
    }
}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //建立3我的,去參加吃蘋果大賽
        Apple apple=new Apple();
        new Thread(apple,"許嵩").start();
        new Thread(apple,"林俊杰").start();
        new Thread(apple,"蜀雲泉").start();

    }
}        

我就加了一個線程休眠,咱們如今來看看輸出的結果是什麼樣的

竟然有0,還有-1 不是已經寫了if(num>0)嗎?出現這種狀況的緣由是,許嵩,林俊杰,蜀雲泉這三個線程都在num>0的時候,例如num=1的時候,他們仨都拿到了資源,均可以去執行run方法

這個時候就會出現這種狀況。通俗一點講許嵩拿到了最後一個蘋果,可是沒吃,林俊杰和蜀雲泉來搶。因爲這個蘋果不具有獨佔性,因此最後一個蘋果被許嵩,林俊杰,蜀雲泉都咬了一口。他們都宣稱本身吃了蘋果,因此就會出現0和-1的狀況。這樣顯然是不容許的。這就是

多線程併發的訪問一個資源產生的安全問題

 要想解決這個問題,就必需要保證蘋果的數量減小必須保證同步,許嵩拿了最後一個蘋果,這個時候蘋果數量同步爲0,剩下的人不能再搶了。

許嵩這個線程在操做的時候,林俊杰和蜀雲泉只能等着。等許嵩操做完了。許嵩,林俊杰和蜀雲泉纔有機會去從新搶資源。

 意思是這樣,方法有3種

 

方法1.同步代碼塊

方法2.同步方法

方法3.鎖機制(Lock)

 

 方法1:同步代碼塊

語法:

 synchronized(同步鎖){

  須要同步操做的代碼

}

同步鎖:爲了保證每一個線程都能單獨的執行操做,java線程同步的機制。同步鎖也叫

同步監聽對象/同步鎖/同步監聽器/互斥鎖

 這些都是別名,就像茴香豆的「茴」字有幾種寫法同樣,都是別名。

Java程序中的任何對象均可以做爲同步監聽對象,可是咱們通常把多個線程同時訪問的共享資源做爲同步監聽對象。監聽其它的單獨的對象有啥意義。注意,在任什麼時候候,最多運行一個線程擁有同步鎖。

代碼以下:

package com.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            //方法1:同步代碼塊
            //因爲我多個線程共享的是Apple對象,因此同步鎖就是this,當前類的對象。不能使用num變量,由於num變量一直在變化
        synchronized (this) {
            if (num > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //返回當前線程的引用Thread.currentThread(),再獲取名字
                System.out.println(Thread.currentThread().getName() + "吃了編號爲" + num-- + "的蘋果");
            }

        }

        }
    }
}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //建立3我的,去參加吃蘋果大賽
        Apple apple = new Apple();
        new Thread(apple, "許嵩").start();
        new Thread(apple, "林俊杰").start();
        new Thread(apple, "蜀雲泉").start();


    }
}

運行結果以下:

這下不會出現搶蘋果事件了,也不會出現數量爲0和-1的狀況了。

 

 方法2:同步方法

synchronize修飾的方法就是同步方法,保證當前線程執行的時候,其它線程只能等待

語法:

synchronize public void Dowork(){

  執行操做

}

同步鎖:對於非static方法,同步鎖就是this,對於靜態方法,同步鎖就是當前方法所在類的字節碼對象(類.class)

注意!不能使用synchronize修飾run方法。

package com.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            eat();
        }
    }

    //方法2:同步方法,直接用synchronized修飾方法
    synchronized private void eat(){
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //返回當前線程的引用Thread.currentThread(),再獲取名字
            System.out.println(Thread.currentThread().getName() + "吃了編號爲" + num-- + "的蘋果");
        }
    }

}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //建立3我的,去參加吃蘋果大賽
        Apple apple = new Apple();
        new Thread(apple, "許嵩").start();
        new Thread(apple, "林俊杰").start();
        new Thread(apple, "蜀雲泉").start();


    }
}

運行結果也是OK的

 

 或許有人會想,既然方法加了個synchronize就線程安全了,那我把全部的方法都加上synchronize不就得了。答案是不行滴

synchronize的優缺點:

優勢:保證了多線程併發訪問時的同步操做,避免了多線程操做的安全問題。

缺點:使用synchronize同步方法/同步代碼塊會致使性能下降。

 

方法3:鎖機制(Lock)

鎖機制用到的是Lock這個接口,固然咱們在寫代碼的時候使用的是Lock接口的一個實現子類,叫ReentrantLock

語法:

private final Lock lock=new ReentrantLock();

try{

    線程操做代碼

}

catch (InterruptedException e) {

    e.printStackTrace();
}

finally {
    lock.unlock();
}

仍是上面的吃蘋果比賽,使用鎖機制的代碼以下:

package com.day14;


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

class Apple implements Runnable{

    //建立一個Lock接口的實現子類對象。ReentrantLock是Lock接口的一個子類的實現。
    private final Lock lock=new ReentrantLock();

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            eat();
        }
    }

    //方法3:同步鎖(Lock)的方式,這個方式和方法2的同步方式很相似。
     private void eat(){
        //進入方法首先上鎖
        lock.lock();
        if (num > 0) {
            try {
                //返回當前線程的引用Thread.currentThread(),再獲取名字
                System.out.println(Thread.currentThread().getName() + "吃了編號爲" + num-- + "的蘋果");
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                //結束後,記得釋放鎖
                lock.unlock();
            }

        }
    }

}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //建立3我的,去參加吃蘋果大賽
        Apple apple = new Apple();
        new Thread(apple, "許嵩").start();
        new Thread(apple, "林俊杰").start();
        new Thread(apple, "蜀雲泉").start();


    }
}

這個鎖機制是否是和synchronize同步方法很相像,只不過同步方法是synchronize修飾的方法,而鎖機制是在方法裏面上鎖,釋放鎖。

鎖機制和同步代碼塊/同步方法比,範圍更普遍。也就是說鎖機制包括了同步代碼塊和方法,並且範圍更大,更加面向對象。上鎖,釋放鎖都本身來寫,還有建立實現Lock接口的對象。

 

 

用線程實現生產者消費者問題

 

如今來說一個生產者和消費者的問題,講定生產者生產一些東西,放到分享池中,而後消費者去分享池中消費東西,大概就是下圖那樣的展現:

 

這就是咱們的生產者和消費者的模型,咱們要根據這個寫代碼。記得加上上面學習的同步鎖知識。

 

分享池代碼:

package com.day15;

public class ShareResource {

    private String name;
    private String sex;
    private int num;
    private boolean isEmpty=true;


    synchronized public void push(String name,String sex,int num) {
        try {
            while (!isEmpty) {//若是不爲空的時候,生產者線程就等待
                    this.wait();
            }
            //開始生產,生產事後,要isEmpty變成爲空,而後喚醒其它的線程
            this.name=name;
            this.sex=sex;
            this.num=num;
            isEmpty=false;
            this.notifyAll();
        } catch (InterruptedException e) {
                e.printStackTrace();
        }


    }

    synchronized public void popup(){
        try {
            while (isEmpty) {
                this.wait();
            }
            System.out.println(this.name + this.sex + this.num);
            isEmpty=true;
            this.notifyAll();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

 

生產者代碼:

package com.day15;

//生產者線程
public class Producer implements Runnable{

    //分享池對象
    public ShareResource shareResource=null;

    public Producer(ShareResource shareResource) {
        this.shareResource=shareResource;
    }

    @Override
    synchronized public void run() {
        for (int i = 0; i <50 ; i++) {

            if (i % 2==0) {
                shareResource.push("許嵩","男",i);
            }
            else{
                shareResource.push("夢中人","女",i);
            }

        }
    }
}

 

消費者代碼:

package com.day15;

public class Consumer implements Runnable {

    //分享池對象
    public ShareResource shareResource=null;

    public Consumer(ShareResource shareResource) {
        this.shareResource=shareResource;
    }

    @Override
    synchronized public void run() {
        for (int i = 0; i <50 ; i++) {
           shareResource.popup();
        }
    }
}

 

 

執行測試代碼:

package com.day15;

//測試類
public class RunTest {
    public static void main(String[] args) {
        //建立生產者和消費者共同的資源對象
        ShareResource resource=new ShareResource();

        //啓動生產者線程
        new Thread(new Producer(resource)).start();
        new Thread(new Producer(resource)).start();
        //啓動消費者線程
        new Thread(new Consumer(resource)).start();
        new Thread(new Consumer(resource)).start();

    }
}

 

代碼就是這些,我實行的是生產者生產一個東西,消費者就去消費,我這裏是直接打印出來了。生產者生產以後就去wait休息,等到東西沒了纔開始幹活。這裏咱們學到了兩個新的方法

1.wait()方法:線程休眠,除了被喚醒,不然就會一直睡覺休息,和睡美人是差很少了,沒有人喚醒是不會甦醒的。wait方法裏面能夠加參數,毫秒,就是沒人喚醒的話就本身醒(好慘啊...)

2.notifyAll()方法:喚醒除本身之外全部的線程,還有一個方法是notify(),喚醒隨機的一個線程

最後咱們看看輸出的結果:

 

經過上面同步方法synchronized和線程的wait方法,notify方法很好的完成了生產者和消費者的問題。這裏須要說明的是,wait方法和notify方法都必須須要同步鎖,那麼,我如今想用Lock鎖機制去完成生產者消費者問題,那該怎麼辦呢?

 

鎖機制Lock完成生產者和消費者問題

鎖機制Lock是沒法使用wait和notify方法的,那使用鎖機制的線程之間怎麼進行通訊呢?

從Java5開始就爲鎖機制的線程提供了Condition接口,用於線程直接的通訊,主要使用的方法有:

1.await()方法,至關於wait()方法,線程睡眠

2.signal()方法,至關於notify()方法,隨機的喚醒任意一個線程

3.signalAll()方法,至關於notifyAll()方法,喚醒除了本身之外的全部的線程

 

而後代碼其實就修改了分享池的代碼,放出來看一下:

package com.StadyJava.day15;

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

public class ShareResource {

    private String name;
    private String sex;
    private int num;
    private boolean isEmpty=true;
    private final Lock lock=new ReentrantLock();
    //建立lock鎖機制的Condition對象
    private Condition condition=lock.newCondition();

    public void push(String name,String sex,int num) {
        lock.lock();

        try {
            while (!isEmpty) {
                condition.await();
            }
            //開始生產,生產事後,要isEmpty變成爲空,而後喚醒其它的線程
            this.name=name;
            this.sex=sex;
            this.num=num;
            isEmpty=false;
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }


    }

    synchronized public void popup(){
        lock.lock();

        try {
            while (isEmpty) {
                condition.await();
            }
            System.out.println(this.name + this.sex + this.num);
            isEmpty=true;
            condition.signalAll();
        }
        catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}

其實沒什麼大變化,就是方法換了名字而已。

 

 

線程的死鎖

死鎖就是兩個線程互相在等待對方釋放鎖,死鎖的現象一旦出現,是解決不了的,因此死鎖現象只能避免,不能解決。

 關於死鎖,有一個超級經典的例子,就是哲學家就餐問題

 

線程的6種狀態

 以下圖所示,Java線程有6種狀態,如今來介紹一下各類狀態。

 

 

 

線程的核心內庫(幾個重要的方法)

線程有幾個很重要的方法須要講一下

1.線程睡眠,sleep方法

這個方法是否是和上面講的wait方法很像?其實不同,wait睡覺以後,同步鎖就釋放了。sleep睡覺的時候,同步鎖是牢牢的抓住不鬆手的

這個方法大部分用來模擬網絡延遲,由於你刷新網頁的時候不是會轉圈圈嗎,能夠模擬這個。代碼中也有不少地方用這個來寫東西,例如我想模擬一個定時炸彈,我能夠這樣寫代碼:

package com.StadyJava.day15;

public class ThreadDemo {
    public static void main(String[] args)  {
        for (int i = 10; i > 0; i--) {
            System.out.println("離爆炸還有"+i+"秒");
        }
        System.out.println("嘣,爆炸啦");
    }
}

這樣寫,沒問題吧,有問題的,我想定時,可是這個一運行,結果全出來了,不是想要的結果,因此咱們能夠加一個睡眠1秒來實現,代碼以下:

package com.StadyJava.day15;

public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        for (int i = 10; i > 0; i--) {
            System.out.println("離爆炸還有"+i+"秒");
            Thread.sleep(1000);
        }
        System.out.println("嘣,爆炸啦");
    }
}

記得,sleep方法是要拋出異常的

這樣就能夠了,實現了倒計時的效果。還有幾個例子,例如坦克發射子彈,那這個子彈確定是不斷位移的,咱們設置好以後,子彈可能瞬間就打出去了,你根本看不到子彈的運行軌跡。爲了仔細的觀察,或者實現子彈慢速射擊的一個要求,咱們也能夠去sleep一下。還有NBA投籃遊戲也是,均可以去試試。

 

2.聯合線程,join方法

線程的join方法表示一個線程等待另外一個線程完成後才執行。就是說把當前線程和當前線程所在的線程聯合成一個線程。join方法被調用以後,線程對象處於阻塞狀態。

適用於A線程須要等到B線程執行完畢,再拿B線程的結果再繼續運行A線程。寫個代碼

package com.StadyJava.day15;

class Join extends Thread{
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("join"+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        Join joinThread=new Join();
        for (int i = 0; i < 50; i++) {
            System.out.println("main"+i);

            if (i == 1) {
                joinThread.start();
            }
            else if (i == 20) {
                joinThread.join();
            }
        }
    }
}

運行結果以下:

Idea副線程想和主線程搶資源真難。。。。運行了好幾回才搶到。。。咱們能夠看到在主線程等於1的時候,兩個線程開始搶佔資源打印輸出。在主線程爲20的時候,joinThread線程就調用了join方法,這個時候他們倆就變成了聯合線程,主線程main開始進入阻塞狀態,必須等到joinThread線程執行完畢才能夠執行。

 

3.後臺線程

在後臺運行,其目的是爲其餘線程提供服務,也稱爲「守護線程。
JVM的垃圾回收器就是典型的後臺線程。
特色:若全部的前臺線程都死亡,後臺線程自動死亡。
測試線程對象是否爲後臺線程:使用thread.isDaemon()。
前臺線程建立的線程默認是前臺線程,而且當且僅當建立線程是後臺線程時,新線程纔是後臺線程。
設置後臺線程:thread.setDaemon(true),該方法必須在start方法調用前,不然出現IllegalThreadStateException異常。

 

package com.StadyJava.day15;

class Daemon extends Thread{
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(super.getName()+"-"+super.isDaemon()+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 50; i++) {
            System.out.println("main"+i);
            if (i == 1) {
                Daemon daemon=new Daemon();
                daemon.setDaemon(true);//設置線程爲後臺線程,必須先設置後臺線程才能start開啓,先start開啓再設置會報錯
                daemon.start();
                Thread.sleep(10);
            }
        }
    }
}

就一個判斷是不是後臺線程的方法 isDaemon() 和一個設置線程爲後臺線程的方法 setDaemon(true)

若是沒有了前臺線程,後臺線程會死亡。

 

4.線程的優先級

優先級有兩個方法

1.setPriority() 獲取當前線程的優先級,main線程的優先級是5,默認的都是5

2.setPriority() 設置線程的優先級,不一樣的操做系統是不同的,Linux和Windows都不同。可是有3個數字是統一的,分別是1,5,10  1是最低,10是最高。因此用這3個數字就能夠了。

 

注意:並非優先級高的線程必定先執行,而是說這個線程有更多的機會去執行。執行的概率大了一些。

下面看一個代碼

package com.StadyJava.day15;

class PriorityThread extends Thread{

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

    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("我是"+getName()+i);
        }
    }
}

public class Priority {
    public static void main(String[] args) {
        PriorityThread priorityThread1=new PriorityThread("優先級高的線程");
        PriorityThread priorityThread2=new PriorityThread("優先級低的線程");
        //priorityThread1.setPriority(Thread.MIN_PRIORITY);也可使用這個,可是數字更簡單
        priorityThread1.setPriority(10);
        priorityThread2.setPriority(5);
        priorityThread2.start();
        priorityThread1.start();
    }
}

看結果也知道,優先級高並非必定先執行。

 

5.線程的禮讓

古有孔融讓梨,那麼線程也有一個禮讓的方法叫 yield方法。這個方法比較特別,他把本身執行的機會讓給那些優先級高的線程,提出這個請求給調度器CPU,可是CPU能夠贊成這個請求也能夠無視這個請求

這個yield方法和sleep方法的區別以下:

1.均可以使得當前處於運行狀態的線程放棄執行的機會,讓給其它線程

2.sleep方法會讓給其它線程,隨機的讓。yield方法會讓給那些優先級高的線程。

3.調用sleep方法後,線程會進入計時等待狀態。調用yield方法後,線程會進入就緒狀態。

 

這個yield方法通常是不用的。不使用。。。。。在調試和測試線程的時候,可能會重現多線程的錯誤,可能。。。。因此仍是瞭解一下就好吧

 

6.線程的定時器和線程組

定時器,就是定時去執行啦,直接看代碼

package com.StadyJava.day15;
import java.util.Timer;
import java.util.TimerTask;

class Vae extends TimerTask {
    public void run() {
        System.out.println("你們好,我是Vae");
    }
}

public class TimerDemo {
    public static void main(String[] args) {
          new Timer().schedule(new Vae(),3000,1000);
    }
}

注意,個人Vae類是繼承的TimeTask類,不是Thread類。定時器的方法就是schedule方法,第一個參數就是TimeTask對象,第二個參數就是第幾秒出現執行,第三個參數就是間隔多少秒執行一次。

 

線程組,就是多個相同的線程在一個組裏面,就像老師講課,給A同窗講一遍,再給B同窗講一遍,再給C同窗講一遍。。。。這樣太麻煩。直接讓ABC三個同窗都過來,一塊兒聽就完事了。這就是線程組的意義

線程組的特色:

1.若是線程A建立了線程B,那麼B和A必定是一組的。

2.一個線程一旦加入了線程組,一生就是這個組的,一天是不良人,一生都是不良人。

當Java程序運行時,JVM會建立一個main線程組,默認全部的線程都是main線程組的

 

 

線程的知識差很少就這些了,下面來問幾個問題,看看都能不能回答

1.synchronized和Lock的區別是什麼?

  synchronized是一個修飾符,Lock是一個類。這是最本質的區別。

2.爲何wait方法和notify方法/notifyAll方法不在Thread類中,而在Object類中?

簡單的說,因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中,由於鎖屬於對象。

相關文章
相關標籤/搜索