java高級--------多線程的學習

java多線程是java高級階段的知識點,也是java中比較難學的一部分,今天我們來初步學習一下java的多線程知識。說在前面,只是簡單的入門,更高深的講解可以參考其它大牛博文。
一、首先對於學習多線程有必要先了解一些概念:
1、什麼是並行?什麼是併發?
並行是指真正意義上的在同一時刻同時執行多個任務,比如多處理機的電腦就可以在同一時刻處理多個任務。
併發是虛擬上的同時執行,比如我們平時玩電腦,同時聽歌,玩遊戲,看電視,貌似是同時進行的,但是其實沒有,只是cpu工作切換的時間很快,我們根本無法感受到,導致我們認爲是同時進行的。併發的意義就在於充分利用cpu資源。對於java中的多線程就是併發程序。在某一刻只能有一個任務執行,cpu調度任務執行。
2、進程、線程、程序的區別?
首先說程序,它是靜態的,它只是一連串的字符組成的代碼塊
進程:進程是操作系統的資源分配的基本空間,操作系統會爲其分配對應的內存空間,而且進程有自己的代碼和數據空間。不存在沒有線程的進程
線程:線程是cpu調度的基本單位,沒有自己的內存空間,線程可以理解爲輕量級的進程,一個進程中可以有多個線程。
二、多線程實現的兩者方式:
繼承Thread類和實現Runnable接口

package com.yxc.thread;

/**
 * 多線程的學習
 * 創建多線程的兩種方式
 * 第一繼承Thread類
 * 第二種實現Runable接口
 */
public class MyThread extends Thread{
    private String name;
    //通過構造方法進行初始化工作
   public MyThread(String name){
       this.name=name;
   }
   //繼承了Thread類,可以重寫裏面的run方法,這個方法是多線程的核心方法
   public void run(){
       //這個線程的工作根簡單,就是從1打印到19
       System.out.println(name+"開始執行");
       for(int i=0;i<20;i++){
           System.out.println(i);
           try {
               Thread.sleep(20);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       System.out.println(name+"結束執行");
   }
}

package com.yxc.thread;

/**
 * 通過實現接口實現多線程
 */
public class MyThread2 implements Runnable{
    private String name;
    public MyThread2(String name){
        this.name=name;
    }
    @Override
    public void run() {
        System.out.println(name+"開始執行");
        for(int i=0;i<10;i++){
            System.out.println("java多線程的學習 使用runnable實現");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name+"結束執行");
    }
}

package com.yxc.thread;

/**
 * 大部分情況下,我們都使用第二種方式實現多線程
 * 因爲在java中繼承是有限制的,那就是單繼承,如果繼承了這個類以後,如果這個
 * 類還想繼承其它的類,那麼就不可以,但是實現接口可以實現多個。
 */
public class TestThread {
    public static void main(String[] args) {
        //下面是實現兩者多線程方式的測試代碼
        MyThread myThread = new MyThread("打印線程");
        //對於實現接口來說。最終還是要通過Thread類來創建執行多線程的對象
        MyThread2 myThread2=new MyThread2("輸出線程");
        Thread thread=new Thread(myThread2);
        //開啓線程一定是調用start()方法,而不是通過對象調用run方法
        myThread.start();
        thread.start();
        //最後的執行效果的肯定是沒有規律的,因爲多線程的執行情況我們根本無法預知,是操作系統隨機調度的
        //後面的學習中還會涉及到這個概念
    }
}

這裏面涉及到幾個常用的多線程中的方法:
start():開啓線程,注意開啓線程不一定就開始執行了,只是進入就緒態,要想真正執行需要cpu的調度
Sleep():休眠,傳入一個毫秒數,表示當前線程休眠一定時間後又進入就緒態

三、線程的五種狀態
在這裏插入圖片描述
下面通過一段案例來了解這幾種狀態的具體情況:

package com.yxc.thread;

/**
 * 線程的五種狀態的瞭解
 */
public class ThreadStatusDemo {
    public static void main(String[] args) {
        PrintStar printStar=new PrintStar();
        System.out.println("線程新建狀態");
        Thread thread=new Thread(printStar,"數星星線程");
        thread.start();
        System.out.println("線程進入就緒轉狀態");
        //這個時候還不一定指定,要等待cpu調用才進入真正的運行態
    }
}
//一個數星星的線程
class PrintStar implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"線程進入運行態");
        for(int i=1;i<100;i++){
            System.out.println("第"+i+"顆星星");
            try {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName()+"線程進入阻塞態");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程阻塞結束進入就緒態");
        }
        System.out.println(Thread.currentThread().getName()+"線程結束");
    }
}

4、幾種方法的學習以及對比:
sleep():讓當前線程休眠指定的時間,線程進入阻塞態,時間到以後又進入就緒態,開始搶佔cpu資源
yield ():這個方法比較有意思,表面上是讓出,讓其它線程執行,但是它讓出以後又馬上進入就緒態,同其它的線程搶佔資源,可以理解爲給你們個機會,公平競爭。
join(); 等待其它的線程執行完以後再執行,前面的兩個方法都是屬於類方法,這個是實例方法,要通過對象來調用的,這個方法是用在其它線程裏面的,通過單詞join(加入)也可以理解,強行加入到某一個線程中去執行
wait():實例方法,調用這個方法以後線程進入阻塞狀態,而且需要等待其它線程的喚起才能進入就緒態,而且調用它,資源會被釋放

package com.yxc.thread;

public class MthodDemo {
    public static void main(String[] args) {
        Thread thread=new Thread(new MyThread1());
        thread.start();
        for(int i=100;i<120;i++){
            if(i==105) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i);
        }
    }
}
class MyThread1 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            System.out.println(i);
        }
    }
}

在代碼中join()方法加入到了main線程中去,當main線程中的執行到105的時候將不再執行,線程將強行加入到main線程中執行。

5、經典的生產者與消費者模式:
下面通過代碼實現多線程最經典的生產者與消費者模式,通過兩個線程之前的通知等待來實現,也學習一下wait方法和notify()、notifyAll()三個方法的使用。我們寫一個簡單的,一個機器負責生產和消費,然後兩個線程去執行,話不多說,直接上代碼。

package com.yxc.producerAndConsumer;

import java.util.ArrayDeque;
import java.util.Queue;

/**
 * 這個類是生產和消費冰淇淋的機器類
 */
public class IceMachine {
    //定義一個機器中最大的容量
    private static final int MAX_BUFFER=20;

    //定義一個隊列來存放冰淇淋
    Queue<Integer> IceQueue=new ArrayDeque<>();
    /**生產者生產冰淇淋的方法*/
    public synchronized void produceIce(){
        //如果機器中的數量達到了上線,那麼通知生產者暫時不要生產
        //直接調用wait()方法就可以
        while(IceQueue.size()==MAX_BUFFER){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int count=0;
        //如果機器中數量沒有達到限定,那麼就不斷的生產
        while(IceQueue.size()<MAX_BUFFER){
            //隨機產生一個1~100整數
            int i = (int)(Math.random()*100+1);
            //將編號爲I的冰淇淋添加到隊列中,等待消費者的消費
            IceQueue.offer(i);
            count++;
        }
        System.out.println("入庫了"+count);
        //通知消費者可以消費了
        notify();
    }

    /**消費者消費方法*/
    public synchronized int ConsumeIce(){
        //如果隊列中的產品數量爲零,那麼通知消費者等待
        while(IceQueue.size()==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Integer i = IceQueue.poll();
        System.out.println(i+"號產品被消費");
        return i;
    }
}

package com.yxc.producerAndConsumer;

/**
 * 消費者線程
 */
public class Consumer implements  Runnable{
    //將生產機器傳進來
   private  IceMachine iceMachine=null;
    //通過構造方法初始化
    public Consumer(IceMachine iceMachine){
        this.iceMachine=iceMachine;
    }
    @Override
    public void run() {
        System.out.println("消費者開始消費產品");
        while(true){
            try {
                iceMachine.ConsumeIce();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

package com.yxc.producerAndConsumer;

/**
 * 生產者線程
 */
public class Producer implements Runnable{
    //將生產機器傳進來
    private IceMachine iceMachine=null;
    //通過構造方法初始化
    public Producer(IceMachine iceMachine){
        this.iceMachine=iceMachine;
    }
    @Override
    public void run() {
        System.out.println("生產者開始生產產品");
        while(true){
            try {
                iceMachine.produceIce();
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

package com.yxc.producerAndConsumer;

/**
 * 測試類
 */
public class TestMain {
    public static void main(String[] args) {
        IceMachine iceMachine=new IceMachine();
        Producer produce=new Producer(iceMachine);
        Consumer consumer=new Consumer(iceMachine);
        new Thread(produce).start();
        new Thread(consumer).start();
    }
}

部分實現的結果圖:
在這裏插入圖片描述
6、線程池
*概念:*池顧名思義就是由很多線程組成的一個集合,系統啓動時系統就創建了很多空的線程保存在線程池中,程序將一個任務傳給線程池時,系統就會在線程池中尋找空閒的線程來執行這個任務。
優勢:大量的創建線程、釋放線程需要花費很多的系統資源,有了線程池以後,有效的提高了性能。減少了資源的消耗
線程池工作原理:任務直接提交給線程池,又線程池尋找空閒線程執行任務,執行完以後,線程又返回空的狀態,返回線程池中,等待下一個任務的執行。一個任務只能有分配一個線程,但是可以同時向線程池發送多個任務。
下面通過代碼實現集中常見的創建線程池的方法
爲了方便,我將四種常見的創建方法以靜態方法的形式實現,然後在主方法中直接調用四種方式,每一種都寫了註釋。

package com.yxc.threadPool;

import java.util.concurrent.*;

/**
 * 線程池的學習使用
 */

public class ThreadPoolDemo {
    public static void main(String[] args) {
        test1();
        test2();
        test3();
        test4();
    }
    /**創建指定個數的線程池*/
    public static void test1(){
        //創建一個容量爲3的線程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for(int i=0;i<10;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"線程被執行");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    /**執行線程池的個數,而且有定時效果的 */
    public static void test2(){
        //創建一個這樣的線程池,可以定時以及週期性執行任務
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延遲一秒以後每兩秒執行一次");
            }
        },1,2,TimeUnit.SECONDS);//這裏兩個參數 延時時間和週期時間
    }
    /**創建一個單例線程,線程池中只有一個線程可以執行任務,這樣符合先來先執行的規則*/
     public static void test3(){
         ExecutorService executorService = Executors.newSingleThreadExecutor();
         for(int i=0;i<10;i++){
             int index=i;
             executorService.execute(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(index);
                     try {
                         Thread.sleep(20);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             });
         }
     }
     /**帶緩存的線程池,如果這個線程被用過,直接分配,如果沒有被用過,那麼就生成一個新的線程分配給任務,然後將線程添加到線程池中*/
     public static void test4(){
         ExecutorService executorService = Executors.newCachedThreadPool();
         for(int i=0;i<10;i++){
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             executorService.execute(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(Thread.currentThread().getName()+"在被執行");
                 }
             });
         }
     }
}

代碼執行結果:
test1:
在這裏插入圖片描述
test3:
在這裏插入圖片描述
test4:
在這裏插入圖片描述
對於測試2,代碼延遲一秒執行,然後之後每兩秒執行一次,無限循環下去
當然對於多線程這一章,代碼一樣,很可能執行的結果是不一樣的,總結它是一個不靠譜的東西。這一章還有一些方法,沒有說明,比如設置權限,設置權限只能優先級高一點,但是不能保證一定被執行。還有設置守護線程等等。

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