JAVA多線程教程

前言

衆所周知,每年的雙十一,十二,以及六一八等,都是巔峯級別的併發問題,同一時間段甚至每秒都有成千上萬請求,那麼是什麼在支撐着這些大量的請求呢?是多線程,簡而言之就是多個程序同時在運行~~,本文會涉及多線程基本使用以及API概述

線程創建方式

Thread方式

描述

Thread相當於線程的接口,可以說每個線程執行都離不開它,歸根結底,都是它在執行線程任務~

一:匿名內部類方式

        new Thread(){
            //內置有一個run方式:該方法是線程執行時業務處理~
            @Override
            public void run() {
                System.out.println("匿名內部類線程聲明執行"+"線程名稱\t"+this.getName());
            }
            //調用start方法,即觸發當前線程開始執行
        }.start();

運行結果

匿名內部類線程聲明執行線程名稱 Thread-0

二:父子繼承方式

//繼承Thread 重寫線程開啓時執行的run方法並打印當前線程名稱
public class MyThread  extends Thread{
    @Override
    public void run() {
        System.out.println("父子繼承方式線程名稱\t"+this.getName());
    }
    //聲明構造方法 外部使用當前bean時直接開啓線程即調用run方法~
    public MyThread(){
        this.start();
    }
    public static void main(String[] args) {
        new MyThread();
    }
}

運行結果

父子繼承方式線程名稱 Thread-0

Runnable接口方式

代碼如下

//實現runnable接口重寫run方法
 class MyThread2  implements Runnable{
     @Override
     public void run() {
         System.out.println("實現runnable接口方式線程名稱\t"+Thread.currentThread().getName());
     }
     public static void main(String[] args) {
         //聲明一個線程對象並引用接口方法的執行類作爲線程進行開啓
         new Thread(new MyThread2()).start();
     }
 }

運行結果

實現runnable接口方式線程名稱 Thread-0

拓展:有人會問爲什麼線程類可以去引用別的類作爲線程並執行呢?OK跟進底層源碼進行查看如下
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

Callable接口方式(帶有返回值的線程任務)

這個應該是實際當種運用最爲廣泛的,因爲前兩種我們發現它都沒有返回值,有時候線程處理任務都需要一個返回值進行邏輯業務處理,而Callable的出現真是如大旱望甘霖啊~

代碼如下

 //實現Callable 泛型類型爲String(字符串)
 class MyThread3  implements Callable<String> {
     @Override
     public String call() throws Exception {
         //獲取隨機數
         return String.valueOf((int)(Math.random()*10+1));
     }
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         //聲明一個默認線程池
         ExecutorService executorService = Executors.newCachedThreadPool();
         //由線程池提交任務執行並返回線程執行完畢之後的結果值
         Future<String> submit = executorService.submit(new MyThread3());
         //調用Future get方法獲取結果
         System.out.println(submit.get());
     }
 }

運行結果

10之內的任何一個隨機數字

拓展

  • Future是什麼

Future表示異步計算的結果。 提供方法以檢查計算是否完成,等待其完成,以及檢索計算結果。 只有在計算完成時才能使用方法get檢索結果,必要時將其阻塞直到準備就緒。

異步說明:

例如,去飯店喫飯,點了湯和主食,主食需要5分鐘,而湯只需要2分鐘,如果廚師是同步即一次只能幹一件事等主食坐好再去做湯由此是7分鐘,倘若廚師是異步,在做主食的同時,閒下來的時間去把湯熬上,這樣的話 會盡可能減少時間快速做好,~~

API

變量和類型 方法 描述 
boolean cancel​(boolean mayInterruptIfRunning) 嘗試取消執行此任務。  
V get() 如果需要等待計算完成,然後檢索其結果。  
V get​(long timeout, TimeUnit unit) 如果需要,最多等待計算完成的給定時間,然後檢索其結果(如果可用)。  
boolean isCancelled() 如果此任務在正常完成之前取消,則返回 true 。  
boolean isDone() 如果此任務完成,則返回 true 。  

更多操作可以依靠API進行一些案例測試

線程等待與通知(通信)

代碼如下
注意:凡是使用線程wait與notify線程之間任務執行必須使用synchronized鎖進行同步
原因:就當前Thread線程方式爲例,它是異步交替執行,可能執行一個線程期間其它任務直接喚醒然後另一個又進入交替執行導致線程死鎖或數據紊亂等問題,加synchronized的目的就是正確執行wait方式時,必須獲取當前對象監聽器,wait等待時會釋放監聽器由其它線程執行任務時可以獲取監聽器並嘗試對它進行喚醒。

class HAHA {
    static class MyThread2 extends Thread {
        private Object o;
        public MyThread2(Object oo) {
            this.o = oo;
        }
        int x = 10;
        @Override
        public void run() {
            synchronized (o) {
                while (true) {
                    if (x > 0) {
                        System.out.println(System.currentTimeMillis()+"我執行了自減操作" + x--);
                        if (x == 5) {
                            try {
                                //等待
                                System.out.println(System.currentTimeMillis()+"我執行了等待操作");
                                o.wait();
                            } catch (InterruptedException e) {
                            }
                        }
                    }else {
                        break;
                    }
                }
            }
        }

    }
    static class MyThread9 extends Thread {
        private Object o;
        public MyThread9(Object oo) {
            this.o = oo;
        }
        @Override
        public void run() {
            synchronized (o) {
                o.notify();
                System.out.println(System.currentTimeMillis()+"當前值是5,線程等待被喚醒");
                System.out.println("準備休眠兩秒再喚醒當前線程");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
            }
        }
    }
    public static void main(String[] args) {
        Object o = new Object();
        MyThread2 myThread2 = new MyThread2(o);
        MyThread9 myThread9 = new MyThread9(o);
        new Thread(myThread2).start();
        new Thread(myThread9).start();
    }
}

運行結果
可以看到,期間等於5的時候線程處於等待狀態,然後隔了兩秒線程被喚醒

1593596066941我執行了自減操作10
1593596066941我執行了自減操作9
1593596066941我執行了自減操作8
1593596066941我執行了自減操作7
1593596066941我執行了自減操作6
1593596066941我執行了等待操作
1593596066999當前值是5,線程等待被喚醒
準備休眠兩秒再喚醒當前線程
1593596068999我執行了自減操作5
1593596068999我執行了自減操作4
1593596068999我執行了自減操作3
1593596068999我執行了自減操作2
1593596068999我執行了自減操作1

線程安全與處理

多線程也有弊端,如果是在高併發情況下訪問頻率過高或程序運行過慢會導致數據紊亂或死鎖的產生,因此要想用好多線程就必須在保證數據安全並且程序運行狀態優良的情況下合理使用纔可~

代碼如下

//使用實現Runnable方式創建線程任務並執行
public class MyRunable implements Runnable {
    int counts=15;
    @Override
    public  void run() {
            while (true) {
                if (counts <= 0) {
                    break;
                }
                //模擬網絡延遲這裏休眠0.5
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + counts);
                counts--;
            }
        }
    public static void main(String[] args) {
        MyRunable myRunable = new MyRunable();
        new Thread(myRunable,"張三").start();
        new Thread(myRunable,"李四").start();
    }
}

運行結果

張三 15
李四 15
張三 13
李四 13
李四 11
張三 11
張三 9
李四 9
張三 7
李四 7
李四 5
張三 5
張三 3
李四 2
張三 1
李四 1

可想而知,肯定會出現數據紊亂,因爲線程與線程之間沒有任何安全保障,誰搶到了CPU的資源即都可以訪問,換而言之,同一時間內當我一個線程正在操作這個counts–還沒結束的時候另外一個線程就已經在處理了,由此導致數據沒有準確性~

解決方法-synchronized

//用synchronized包裹計算方法體,鎖對象爲當前實例Bean,前提你在運行時一定要是同一個對象~
    public  void run() {
            while (true) {
                synchronized (this) {
                    if (counts <= 0) {
                        break;
                    }
                    System.out.println(Thread.currentThread().getName() + "\t" + counts);
                    counts--;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

**作用:

實現線程期間的同步作用,只要是它修飾的方法或對象,線程執行期間每次只能進入一個線程執行,期間其它線程都在排隊等待,從而不會出現串行執行的問題保證數據的安全性**

用法:

指定加鎖對象:給指定對象加鎖,進入同步代碼時需要獲取鎖纔可執行
指定實例方法:給當前實例方法加鎖,方法上鎖不能串行訪問只能等待方法執行完畢即執行下一次
指定靜態方法:對當前類加鎖,進入同步代碼前需要獲取當前鎖即可運行~

拓展-lock鎖
synchronized對比lock區別

synchronized:是java中關鍵字,由jvm維護,由底層自動加鎖以及執行完畢之後的釋放鎖等一系列操作
lock:jdk1.5後推出的類,提供對應API,是API層面鎖形式,由開發組手動去獲取鎖並釋放鎖,

對比上述實例-簡單改造如下

//聲明鎖對象-在進行計算操作時打開鎖-執行完畢之後釋放鎖(這裏採用finally,程序運行期間很可能各種異常bug,異常出現之後鎖不會釋放所以這裏使用這個可以讓程序不管運行還是失敗都會釋放鎖~)
public class MyRunable implements Runnable {
    ReentrantLock lk = new ReentrantLock();
    int counts = 15;

    @Override
    public void run() {
        while (true) {
            lk.lock();
            try {
                if (counts <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "\t" + counts);
                counts--;
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lk.unlock();
            }
        }
    }
    public static void main(String[] args) {
        MyRunable myRunable = new MyRunable();
        new Thread(myRunable, "張三").start();
        new Thread(myRunable, "李四").start();
    }
}

線程池使用

實際使用中肯定不能用它默認的線程池,首先擴展性不好,針對業務不同那各項的配置要求參數也不同,所以我們需要自己定義線程池
可以參照我的另外一篇實際開發中用到的線程池系列文章

開發中線程不安全的API

併發高的情況下,如下幾個API都是不安全的

  • ArrayList
    聲明實例時,使用Vector代替
//Vector的底層中 元素方法加了同步鎖
List<String> list = new Vector<>();
  • HashSet
Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
Set<String> set3 = new CopyOnWriteArraySet<>();
  • HashMap
Map<String, String> map2= Collections.synchronizedMap(new HashMap<>());
Map<String, String> map3 = new ConcurrentHashMap<>();

其它還有,凡是不安全的API都要考慮線程安全問題,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章