多執行緒基礎知識

2020-09-19 12:04:50

基本概念

  1. 程式: 為完成特定任務,用某種語言編寫的一組指令的集合,即指一段靜態的程式碼,靜態物件;
  2. 程序: 是程式的一次執行過程,或是正在執行的一個程式,是一個動態的過程:有它自身的產生、存在和消亡的過程----生命週期;
  3. 執行緒 : 程序可以進一步細化為執行緒,是程式內部的一條執行路徑,一個程式可以有多條執行路徑;

程式是靜態的,程序是動態的;

程序作為資源分配單位,系統在執行時會為每個程序分配不同的記憶體區域;

執行緒作為排程和執行的單位,每個執行緒擁有獨立的執行棧和程式計數器;

一個程序中的多個執行緒共用相同的記憶體單元/記憶體地址空間–它們從同一堆中分配物件,可以存取相同的變數和物件,這就是執行緒間的通訊更加的簡便、高效,但是多執行緒操作共用的系統資源可能帶來安全隱患;

多執行緒的建立方式

面試題:建立多執行緒的方式有幾種?
答:四種;
1). 繼承Thread類的方式建立執行緒;
2). 實現Runnable介面建立執行緒;
3). 使用Callable和Future建立執行緒;
4). 使用執行緒池例如用Executor框架;

1.繼承Thread類的方式建立執行緒:

public class Main01 {

    public static void main(String[] args) {
        //給主執行緒命名:Thread.currentThread()是獲取當前執行緒
        Thread.currentThread().setName("主執行緒");
        //列印
        System.out.println(Thread.currentThread().getName()+"執行緒執行了");
        //建立一個執行緒
        MyThread myThread = new MyThread();
        //啟動執行緒
        myThread.start();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"執行緒執行了");
    }
}
//輸出
 	主執行緒執行緒執行了
	Thread-0執行緒執行了

2.實現Runnable介面的方式建立執行緒:

public class Main02 {

    public static void main(String[] args) {
        //給main執行緒命名
        Thread.currentThread().setName("主執行緒");
        //列印
        System.out.println(Thread.currentThread().getName()+"執行了");

        //建立執行緒
        MyRunnable myRunnable = new MyRunnable();
        //將myRunnable傳入Thread;類中
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"執行了");
    }
}
//輸出
 	主執行緒執行緒執行了
	Thread-0執行緒執行了

3.使用Callable和FutureTask建立執行緒

public class Main03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
   		 //建立實現類
        MyCallable myCallable = new MyCallable();
		//建立FutureTask類,該類實現了Runnable介面;
        FutureTask stringFutureTask = new FutureTask(myCallable);
        //將建立的FutureTask的範例傳遞給Thread類,並啟動執行緒
        new Thread(stringFutureTask).start();

        Object o = stringFutureTask.get();

        System.out.println(o);


    }
}
//MyCallable介面實現Callable介面,其中的call方法相當於我們的run方法
class MyCallable implements Callable{

    @Override
    public Object call() throws Exception {
        int h = 0;
        for(int i = 1; i <100; i++){
            if(i % 2 == 0){
                h+=i;
            }
        }
        return  Thread.currentThread().getName()+"求和為:--"+h;
    }
}

4.使用執行緒池

public class Main04 {

    public static void main(String[] args) {
        //1.提供指定執行緒數量的執行緒池;
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //2.執行指定的執行緒操作,需要提供實現Runnable介面和Callable介面的實現類物件;
        executorService.execute(new MyRunnable1());

        //關閉連線池;
        executorService.shutdown();

    }
}
class MyRunnable1 implements Runnable {
    @Override
    public void run() {

        System.out.println("這是使用執行緒池建立執行緒");

    }
}
使用執行緒池的好處:
	1.提高響應速度(減少了執行緒的建立時間)
	2.降低了資源消耗(重複利用執行緒池中的執行緒,不需要每次都建立)
	3.便於管理:	
		corePoolSize:核心池的大小
		maximumPoolSize:最大執行緒數;
		keepAliveTime:執行緒沒有任務是最多保持多長時間後終止

多執行緒的常用方法

1.start():啟動當前執行緒;呼叫當前執行緒的run();
2.run(): 通常需要重寫Thread類中的此方法,將要執行的操作寫入該方法中;
3.currentThread(): 靜態方法,返回執行當前程式碼的執行緒;
4.getName():獲取當前執行緒的名字;
5.setName():設定當前執行緒的名字;
6.yield():釋放當前cpu的執行權;
7.join():線上程A中呼叫執行緒B的join(),此時執行緒A就會進入阻塞狀態,直到執行緒B完全執行完以後,執行緒A才結束阻塞狀態;
8.stop():已過時,當執行此方法是,強制結束當前執行緒;
9.sleep(long millitime) : 使當前執行緒睡眠指定的毫秒數,在此期間,該執行緒處於阻塞狀態;
10. isAlive(): 判斷當前執行緒是否存活;

多執行緒的生命週期

執行緒生命週期圖

執行緒的安全問題

1.多個執行緒執行的不確定性造成執行結果的不穩定
2.多個執行緒對賬本的共用,會造成操作的不完整性,會破壞資料;

解決執行緒的安全問題

解決執行緒安全的方式有幾種?
答:3種;
1.同步程式碼塊—synchronized(同步監視器){需要被同步的程式碼}

public class SYNdemo01 {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);

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

    }
}
class MyRunnable implements Runnable{
    private int x = 150;
    @Override
    public void run() {
        while (true){
            
            synchronized (this){
                if(x>0){
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"視窗賣出了:"+x+"號票");
                    x--;
                }else{
                    break;
                }
            }
        }
    }
}
synchronized(){需要被同步的程式碼}
	1.操作共用資料的程式碼,即需要被同步的程式碼;
	2.共用資料,多個執行緒同時操作的變數,比如我們現實中的火車票
	3.同步監視器:俗稱:,任何一個類的物件,都可以充當鎖物件;
		要求:多個執行緒必須要共用一把鎖,即這個鎖物件在多執行緒操作時必須是同一個鎖物件;

2.同步方法—被synchronized修飾的方法

public class SYNdemo01 {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);

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

    }
}
class MyRunnable implements Runnable{
    private int x = 150;
    @Override
    public void run() {
        while (true){

            loket();
            if(x<=0){
                break;
            }
            
        }
    }

    //同步方法
    public synchronized void loket(){
        if(x>0){
            try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"視窗賣出了:"+x+"號票");
            x--;
        }

    }
}

3.Lock鎖解決執行緒安全問題
Lock鎖是JDK5.0後加入的,是一個介面,比較常用的是ReentrantLock類,他擁有與synchronized相同的並行性和記憶體語意,在實現執行緒安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖;

public class LockSuo {

    public static void main(String[] args) {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);

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

class Window implements  Runnable{

    private static int x = 150;
    private ReentrantLock reentrantLock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try{
                reentrantLock.lock();

                if(x>0){

                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"視窗出售了:"+x+"號票");
                    x--;
                }else{
                    break;
                }
            }finally {
                reentrantLock.unlock();
            }
        }
    }
}

執行緒安全的單例模式之懶漢式

class BankTest{
    private static BankTest bankTest;

    private BankTest(){

    }
    public static BankTest getBankTest(){
        synchronized (BankTest.class) {
            if (bankTest == null) {

                bankTest = new BankTest();
            }
        }
        return bankTest;
    }
}

執行緒中的死鎖問題

死鎖: 不同的執行緒分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了執行緒的死鎖;
死鎖出現後,不會出現異常,不會出現提示,只是所有的執行緒都處於阻塞狀態,無法繼續;
死鎖演示:

public class SYNdemo02 {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
    }
}

執行緒的通訊

例題:交替列印1-100;執行緒1和執行緒2交替列印;

public class JT {
    public static void main(String[] args) {

        Number number = new Number();
        Thread thread1 = new Thread(number);
        Thread thread2 = new Thread(number);

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

class Number implements  Runnable {
    private static int x = 1;
    
    @Override
    public void run() {
        while (true) {
           synchronized (this) {
               if (x <= 100) {
                   notify();
                   try {
                       Thread.currentThread().sleep(100);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName() + "列印了:" + x);
                   x++;
                   try {
                       wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               } else {
                   break;
               }
           }

        }

    }
}