Java入門系列-21-多線程

什麼是線程

在操做系統中,一個應用程序的執行實例就是進程,進程有獨立的內存空間和系統資源,在任務管理器中能夠看到進程。java

線程是CPU調度和分派的基本單位,也是進程中執行運算的最小單位,可完成一個獨立的順序控制流程,固然一個進程中能夠有多個線程。編程

多線程:一個進程中同時運行了多個線程,每一個線程用來完成不一樣的工做。多個線程交替佔用CPU資源,並不是真正的並行執行。多線程

使用多線程能充分利用CPU的資源,簡化編程模型,帶來良好的用戶體驗。ide

一個進程啓動後擁有一個主線程,主線程用於產生其餘子線程,並且主線程必須最後完成執行,它執行各類關閉動做。測試

在Java中main()方法爲主線程入口,下面使用 Thread 類查看主線程名。this

public class MainThread {
    public static void main(String[] args) {
        //獲取當前線程
        Thread t=Thread.currentThread();
        System.out.println("當前線程名字:"+t.getName());
        //自定義線程名字
        t.setName("MyThread");
        System.out.println("當前線程名字:"+t.getName());
    }
}

建立線程

在Java中建立線程有兩種方式
1.繼承 java.lang.Thread 類
2.實現 java.lang.Runnable 接口操作系統

1.繼承 Thread 類建立線程

(1)定義MyThread類繼承Thread類線程

(2)重寫run()方法,編寫線程執行體code

public class MyThread extends Thread{

    //重寫run方法
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

(3)建立線程對象,調用start()方法啓動線程對象

public class TestMyThread {

    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        //啓動線程
        myThread.start();
    }
}

多個線程同時啓動後是交替執行的,線程每次執行時長由分配的CPU時間片長度決定

修改 TestMyThread.java 觀察多線程交替執行

public class TestMyThread {

    public static void main(String[] args) {
        MyThread myThread1=new MyThread();
        MyThread myThread2=new MyThread();
        myThread1.start();
        myThread2.start();
    }
}

多運行幾回觀察效果

啓動線程可否直接調用 run()方法?
不能,調用run()方法只會是主線程執行。調用start()方法後,子線程執行run()方法,主線程和子線程並行交替執行。

2.實現 Runnable 接口建立線程

(1)定義MyRunnable類實現Runnable接口

(2)實現run()方法,編寫線程執行體

public class MyRunnable implements Runnable{
    //實現 run方法
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

(3)建立線程對象,調用start()方法啓動線程

public class TestMyRunnable {

    public static void main(String[] args) {
        Runnable runnable=new MyRunnable();
        //建立線程,傳入runnable
        Thread t=new Thread(runnable);
        t.start();
    }
}

線程的生命週期

建立狀態:線程建立完成,好比 MyThread thread=new MyThread

就緒狀態:線程對象調用 start() 方法,線程會等待CPU分配執行時間,並無立馬執行

運行狀態:線程分配到了執行時間,進入運行狀態。線程在運行中發生禮讓 (yield) 會回到就緒狀態

阻塞狀態:執行過程當中遇到IO操做或代碼 Thread.sleep(),阻塞後的線程不能直接回到運行狀態,須要從新進入就緒狀態等待資源的分配。

死亡狀態:天然執行完畢或外部干涉終止線程

線程調度

線程調度指按照特定機制爲多個線程分配CPU的使用權

線程調度經常使用方法

方法 說明
setPriority(int newPriority) 更改線程的優先級
static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠
void join() 等待該線程終止
static void yield() 暫停當前正在執行的線程對象,並執行其餘線程
void interrupt() 中斷線程
boolean isAlive() 測試線程是否處於活動狀態

線程優先級的設置

線程優先級由1~10表示,1最低,默認有限級爲5。優先級高的線程得到CPU資源的機率較大。

public class TestPriority {

    public static void main(String[] args) {
        Thread t1=new Thread(new MyRunnable(),"線程A");
        Thread t2=new Thread(new MyRunnable(),"線程B");
        //最大優先級
        t1.setPriority(Thread.MAX_PRIORITY);
        //最小優先級
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

線程休眠

讓線程暫時睡眠指定時長,線程進入阻塞狀態,睡眠時間事後線程會再進入可運行狀態。

休眠時長以毫秒爲單位,調用sleep()方法須要處理 InterruptedException異常。

public class TestSleep {
    
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            System.out.println("第 "+i+" 秒");
            try {
                //讓當前線程休眠1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

強制運行

使用 join() 方法實現,能夠認爲是線程的插隊,會先強制執行插隊的線程。

public class JoinThread implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println("線程名:"+Thread.currentThread().getName()+" i:"+i);
        }
        System.out.println("插隊線程執行完畢!");
    }   
}
public class TestJoin {

    public static void main(String[] args) {
        Thread joinThread=new Thread(new JoinThread(),"插隊的線程");
        //啓動後與主線程交替執行
        joinThread.start();
        for (int i = 1; i <= 10; i++) {
            if (i==5) {
                try {
                    System.out.println("====開始插隊強制執行====");
                    joinThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("線程名:"+Thread.currentThread().getName()+" i:"+i);
        }
        System.out.println("主線程執行完畢!");
    }
}

最一開始執行,主線程 main 和 "插隊的線程"是交替執行。當主線程的循環到第5次的時候,調用 "插隊的線程"的join方法,開始強制執行"插隊的線程",待"插隊的線程"執行完後,才繼續恢復 main 線程的循環。

線程禮讓

使用 yield() 方法實現,禮讓後會暫停當前線程,轉爲就緒狀態,其餘具備相同優先級的線程得到運行機會。

下面咱們實現Runnable接口,在run方法中實現禮讓,建立兩個線程,達到某種條件時禮讓。

public class YieldThread implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("線程名:"+Thread.currentThread().getName()+" i:"+i);
            //當前線程執行到5後發生禮讓
            if (i==5) {
                System.out.println(Thread.currentThread().getName()+" 禮讓:");
                Thread.yield();
            }
        }
    }
}
public class TestYield {

    public static void main(String[] args) {
        Thread t1=new Thread(new YieldThread(),"A");
        Thread t2=new Thread(new YieldThread(),"B");
        t1.start();
        t2.start();
    }
}

只是提供一種可能,不能保證必定會實現禮讓

線程同步

首先看一個多線共享同一個資源引起的問題

倉庫有10個蘋果,小明、小紅、小亮每次能夠從倉庫中拿1個蘋果,拿完蘋果後倉庫中的蘋果數量-1。

先編寫倉庫資源類,實現接口

//這個實現類將被多個線程對象共享
public class ResourceThread implements Runnable{
    private int num=10;
    @Override
    public void run() {
        while(true) {
            if (num<=0) {
                break;
            }
            num--;
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
        }
    }
}

編寫測試類,建立兩個線程對象,共享同一個資源

public class TestResource {

    public static void main(String[] args) {
        ResourceThread resource=new ResourceThread();
        //使用同一個Runnable實現類對象
        Thread t1=new Thread(resource,"小明");
        Thread t2=new Thread(resource,"小紅");
        Thread t3=new Thread(resource,"小亮");
        t1.start();
        t2.start();
        t3.start();
    }
}

運行後咱們發現,每次拿完蘋果後的剩餘數量出現了問題,使用同步方法能夠解決這個問題。

語法:

訪問修飾符 synchronized 返回類型 方法名(參數列表){
    ……
}

synchronized 就是爲當前的線程聲明一個鎖

修改 ResourceThread.java 實現同步

//這個實現類將被多個線程對象共享
public class ResourceThread implements Runnable{
    private int num=10;
    private boolean isHave=true;
    @Override
    public void run() {
        while(isHave) {
            take();
        }
    }
    //同步方法
    public synchronized void take() {
        if (num<=0) {
            isHave=false;
            return;
        }
        num--;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);
    }
}

實現同步的第二種方式同步代碼塊

語法:

synchronized(syncObject){
    //須要同步的代碼
}

syncObject爲需同步的對象,一般爲this

修改 ResourceThread.java 改成同步代碼塊

//這個實現類將被多個線程對象共享
public class ResourceThread implements Runnable{
    private int num=10;
    private boolean isHave=true;
    @Override
    public void run() {
        while(isHave) {
            synchronized(this) {
                if (num<=0) {
                    isHave=false;
                    return;
                }
                num--;
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"拿走一個,還剩餘:"+num);               
            }
        }
    }
}
相關文章
相關標籤/搜索