僅需這一篇文章帶你入門Java多線程

一、程序、進程、線程的關係

  1. 程序(program)
    是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼。
  2. 進程(process)
    程序的一次執行過程,或是正在運行的一個程序。
    進程作爲資源分配的單位,系統在運行時會爲每個進程分配不同的內存區域
  3. 線程(thread)
    概念:進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
    說明:線程作爲調度和執行的單位,每個線程擁獨立的運行棧和程序計數器(pc),線程切換的開銷小。
    在這裏插入圖片描述

在這裏插入圖片描述

參考上圖
進程可以細化爲多個線程。
每個線程,擁有自己獨立的:棧、程序計數器
多個線程,共享同一個進程中的結構:方法區、堆。

二、並行與併發

  1. 並行: 多個人幹不同的事,如工廠的多條生產線。
  2. 併發:多個人同時做一個事情,如春運搶票。
    在這裏插入圖片描述

三、創建多線程

方式一:繼承java.lang.Thread類

步驟分析:

  1. 創建一個類,繼承Thread。
  2. 重寫run方法。
  3. 創建繼承Thread的對象。
  4. 啓動線程。

代碼實現:

/**
 * 1. 通過 extends Thread 創建線程
 * 2. 創建匿名子類
 */
public class ThreadExtendsTest {
    public static void main(String[] args) {
        //3. 創建繼承Thread的對象
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        //4. 啓動線程
        thread1.start();
        thread2.start();

        //附:創建匿名子類
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }).start();

    }
}
//1. 創建一個類,繼承Thread
class MyThread extends Thread {
    //2. 重寫run方法
    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

方式二:實現java.lang.Runnable接口

步驟分析

  1. 創建實現Runnable接口的實現類。
  2. 重寫run()方法。
  3. 創建實現Runnable接口的實現類。
  4. 創建線程對象,將實現Runnable的實現類的實例化對象傳入構造器。
  5. 啓動線程。

代碼實現

/**
 * 1. 通過implements Runnable 創建線程
 * 2. priority設置線程優先級
 */
public class ThreadImplementsTest {

    public static void main(String[] args) {
        //3. 創建實現Runnable接口的實現類
        ThreadRunnable tr = new ThreadRunnable();
        //4. 創建線程對象,將實現Runnable的實現類的實例化對象傳入構造器 
        //tips: 如將tr傳入
        Thread thread1 = new Thread(tr);
        Thread thread2 = new Thread(tr);

        //5. 啓動線程
        thread1.start();
        thread2.start();

    }
}

// 1. 創建實現Runnable接口的實現類
class ThreadRunnable implements Runnable {
    //2. 重寫run()方法
    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

方式三:實現java.util.concurrent.Callable接口

步驟分析

  1. 創建一個實現Callable的實現類。
  2. 實現call方法,將此線程需要執行的操作聲明在call()中。
  3. 創建Callable接口實現類的對象
  4. 創建FutureTask的對象,將Callable接口實現類的對象傳遞到FutureTask構造器中
  5. 將FutureTask的對象作爲參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()

代碼實現

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableThread {
    public static void main(String[] args) {
        //3.創建Callable接口實現類的對象
        CallableThreads callableThreads = new CallableThreads();
        //4.創建FutureTask的對象,將Callable接口實現類的對象傳遞到FutureTask構造器中
        FutureTask futureTask1 = new FutureTask(callableThreads);
        FutureTask futureTask2 = new FutureTask(callableThreads);
        //5.將FutureTask的對象作爲參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
        Thread thread1 = new Thread(futureTask1);
        Thread thread2 = new Thread(futureTask2);

        thread1.start();
        thread2.start();

    }

}
//1.創建一個實現Callable的實現類
class CallableThreads implements Callable {
    //2.重寫call方法,將此線程需要執行的操作聲明在call()中
    @Override public Object call() throws Exception {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
        return null;
    }
}

方式四:使用線程池

步驟分析

  1. 創建實現Runnable接口的實現類。
  2. 重寫run方法。
  3. 創建指定線程數量的線程池。
  4. 執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象。
  5. 關閉線程池。

代碼實現

import java.util.concurrent.ExecutorService;
import static java.util.concurrent.Executors.newFixedThreadPool;

public class ThreadPool {
    public static void main(String[] args) {
        //3. 創建指定線程數量的線程池
        ExecutorService service = newFixedThreadPool(20);
        //4.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象
        service.execute(new ThreadRunnable2());
        service.execute(new ThreadRunnable2());

        //5.關閉線程池
        service.shutdown();

    }

}
//1. 創建實現Runnable接口的實現類。
class ThreadRunnable2 implements Runnable {
    //2. 重寫run方法
    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

四、Thread類中的常用的方法

  1. start():啓動當前線程;調用當前線程的run()
  2. run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中,在Thread對象中調用則在當前線程中直接調用run()方法,不會創建新線程。
  3. currentThread():靜態方法,返回執行當前代碼的線程
  4. getName():獲取當前線程的名字
  5. setName():設置當前線程的名字
  6. yield():釋放當前cpu的執行權
  7. join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以後,線程a才結束阻塞狀態。
  8. stop():已過時。當執行此方法時,強制結束當前線程。
  9. sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
  10. isAlive():判斷當前線程是否存活
  11. setPriority():設置線程的優先級(優先級從1-10,默認爲5)
  12. getPriority():獲取線程的優先級
    說明:高優先級的線程要搶佔低優先級線程cpu的執行權。但是隻是從概率上講,高優先級的線程高概率的情況下被執行。並不意味着只當高優先級的線程執行完以後,低優先級的線程才執行。


    下面這三個方法定義在java.lang.Object類中。
  13. wait():使當前線程暫停執行並釋放對象鎖
  14. notify():將喚醒被wait()方法暫停執行的一個線程,如果有多個線程被wait,就喚醒優先級高的那個。
  15. notifyAll():將喚醒被wait()方法暫停的所有線程

代碼實現

public class ThreadMethodTest {
    public static void main(String[] args) {
        ThreadMethod threadMethod = new ThreadMethod();

        Thread thread = new Thread(threadMethod);

        // 5. setName():設置當前線程的名字
        thread.setName("線程一");

        // 11. setPriority():設置線程的優先級(優先級從1-10,默認爲5)
        thread.setPriority(7);

        //12. getPriority():獲取線程的優先級
        System.out.println(thread.getName()+" 的優先級爲:"+thread.getPriority());

        // 1. start():啓動當前線程;調用當前線程的run()
        thread.start();
        // 2. run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中
//        thread.run();

        // 10. isAlive():判斷當前線程是否存活
        System.out.println(thread.isAlive());


    }
}

class ThreadMethod implements Runnable {

    @Override public void run() {
        //6. yield():釋放當前cpu的執行權
          Thread.yield();
        //7. join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,
        // 直到線程b完全執行完以後,線程a才結束阻塞狀態。
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 8. stop():已過時。當執行此方法時,強制結束當前線程。
        //Thread.currentThread().stop();

        //9. sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        //3. currentThread():靜態方法,返回執行當前代碼的線程
        //4. getName():獲取當前線程的名字
        System.out.println(Thread.currentThread().getName());
    }
}

五、線程的生命週期

在這裏插入圖片描述

  1. NEW(新建狀態)
  2. RUNNABLE(就緒狀態)
  3. RUNNING(運行狀態)
  4. BLOCKED(阻塞狀態)
  5. TERMINATED(死亡狀態)

六、線程的同步之Synchronized

方式一:同步代碼塊

  1. 操作共享數據的代碼,即爲需要被同步的代碼。儘量只包含需要同步執行的代碼。
  2. 共享數據:多個線程共同操作的變量。
  3. 同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
    要求:多個線程必須要共用同一把鎖。
  4. 補充:①在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器。
    ②在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。

語法

synchronized(同步監視器){
	//需要被同步的代碼
}

class Thread1 extends Thread {
    private static int ticker = 100;
    private static String str = " ";
    @Override
    public void run() {
        while (true) {
        //------------同步代碼塊--------------
            synchronized (str) {
                if (ticker > 0) {
                    System.out.println(Thread.currentThread().getName() + "->" + ticker);
                    ticker--;
                } else {
                    break;
                }
            }
        //------------同步代碼塊--------------
        }
    }
}

方式二:同步代碼方法

如果操作共享數據的代碼完整的聲明在一個方法中,可以將整個方法體聲明爲同步的

  1. 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
  2. 非靜態的同步方法,同步監視器是:this
    靜態的同步方法,同步監視器是:當前類本身

語法

 訪問修飾符 static synchronized 返回值 方法名(){
	//代碼塊
 }

class Thread2 extends Thread {
    private static int ticker = 100;
    @Override
    public void run() {
        while (ticker > 0) {
            show();
        }
    }
    //同步方法 synchronized
    private static synchronized void show(){
        if (ticker > 0) {
            System.out.println(Thread.currentThread().getName() + "->" + ticker);
            ticker--;
        }
    }
}

方式三:Lock

步驟

  1. 實例化Reentrantlock
  2. 調用鎖定方法lock();
  3. 調用解鎖unlock();

示例

import java.util.concurrent.locks.ReentrantLock;

public class ThreadLockTest {
    public static void main(String[] args) {
        ThreadLock threadLock = new ThreadLock();
        Thread thread = new Thread(threadLock);
        thread.start();
    }
}

class ThreadLock implements Runnable {
    private static int ticker = 100;
    //1. 實例化Reentrantlock
    private static ReentrantLock lock = new ReentrantLock();

    @Override public void run() {
        while (ticker > 0) {
            //2. 調用鎖定方法lock();
            lock.lock();
            try {
                show();
            } finally {
                lock.unlock();
            }
        }
    }

    private static void show() {
        if (ticker > 0) {
            System.out.println(Thread.currentThread().getName() + "->" + ticker);
            ticker--;
        }
    }
}

七、線程通信

線程通信涉及到的三個方法:

  • wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。
  • notify():一旦執行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優先級高的那個。
  • notifyAll():一旦執行此方法,就會喚醒所有被wait的線程。

說明:

  • 1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
  • 2.wait(),notify(),notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。
    否則,會出現IllegalMonitorStateException異常
  • 3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章