Java: 線程,線程池

(Java中線程池,你真的會(huì)用嗎?)[https://developer.aliyun.com/article/756612?scm=20140722.184.2.173]
?進(jìn)程:一個(gè)啟動(dòng)的應(yīng)用程序,是線程的載體.進(jìn)程也是程序的一次執(zhí)行過(guò)程,是系統(tǒng)運(yùn)行程序的基本單位;系統(tǒng)運(yùn)行一個(gè)程序即是一個(gè)進(jìn)程從創(chuàng)建、運(yùn)行到消亡的過(guò)程

?線程是系統(tǒng)中最小的執(zhí)行單元.JVM搶占式調(diào)度

?啟動(dòng)應(yīng)用程序啟動(dòng)進(jìn)程,進(jìn)程默認(rèn)情況下會(huì)啟動(dòng)主線程

進(jìn)程與線程的區(qū)別
進(jìn)程:有獨(dú)立的內(nèi)存空間,進(jìn)程中的數(shù)據(jù)存放空間(堆空間和??臻g)是獨(dú)立的,至少有一個(gè)線程。
線程:堆空間是共享的,??臻g是獨(dú)立的,線程消耗的資源比進(jìn)程小的多。

一.多線程的創(chuàng)建方式:

1. 繼承(extends) Thread

  1. 定義Thread類(lèi)的子類(lèi),并重寫(xiě)該類(lèi)的run()方法,該run()方法的方法體就代表了線程需要完成的任務(wù),因此把run()方法稱(chēng)為線程執(zhí)行體。
  2. 創(chuàng)建Thread子類(lèi)的實(shí)例,即創(chuàng)建了線程對(duì)象
  3. 調(diào)用線程對(duì)象的start()方法來(lái)啟動(dòng)該線程
    缺點(diǎn):
    直接將任務(wù)的實(shí)現(xiàn)寫(xiě)到了線程類(lèi)當(dāng)中,耦合度太高
//自定義中斷標(biāo)記,實(shí)現(xiàn)指定時(shí)間中斷
class MyThread extends Thread {
    private boolean needInterrupt = false;
    public void setNeedInterrupt(boolean needInterrupt) {
        this.needInterrupt = needInterrupt;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (needInterrupt) return;
            System.out.println("打印" + i);

            try {
                sleep(1000L);

            } catch (InterruptedException e) {
                System.out.println("報(bào)錯(cuò)" + e.getMessage());
                e.printStackTrace();
            }
        }
    }}
public class Main {
    public static void main(String[] args) {

        MyThread thread = new MyThread();
        thread.setName("ccc");
       // thread.setPriority(10);             設(shè)置線程優(yōu)先級(jí)
        thread.start();

        try {
            thread.sleep(3000L);
            thread.interrupt();
           thread.setNeedInterrupt(true);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }}}

2.實(shí)現(xiàn)(implements) Runnable

  1. 定義Runnable接口的實(shí)現(xiàn)類(lèi),并重寫(xiě)該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
  2. 創(chuàng)建Runnable實(shí)現(xiàn)類(lèi)的實(shí)例,并以此實(shí)例作為T(mén)hread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
  3. 調(diào)用線程對(duì)象的start()方法來(lái)啟動(dòng)線程。
    • Runnable不返回結(jié)果,也不能拋出被檢查的異常
//多線程賣(mài)100張票
public class MyRunnable implements Runnable {
    private int Ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (MyRunnable.class) { //用this也可
                if (Ticket > 0) {
                    System.out.println("賣(mài)出第" + (101 - Ticket) + "張票");
                    Ticket--;
                } else return;
            }

        }
    }
}

public class Main {
    public static void main(String[] args) {
  MyRunnable runnable = new MyRunnable();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);
        thread1.setName("窗口1");
        thread1.setName("窗口2");
        thread1.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}      

如果一個(gè)類(lèi)繼承Thread,則不適合資源共享。但是如果實(shí)現(xiàn)了Runnable接口的話,則很容易實(shí)現(xiàn)資源共享

實(shí)現(xiàn)Runnable接口比繼承Thread類(lèi)所具有的優(yōu)勢(shì)

  1. 適合多個(gè)相同的程序代碼的線程去共享同一個(gè)資源。
  2. 可以避免java中的單繼承的局限性。
  3. 增加程序的健壯性,實(shí)現(xiàn)解耦操作,代碼可以被多個(gè)線程共享,代碼和線程獨(dú)立。
  4. 線程池只能放入實(shí)現(xiàn)Runable或Callable類(lèi)線程,不能直接放入繼承Thread的類(lèi)

2.1 匿名內(nèi)部類(lèi)創(chuàng)建

使用線程的匿名內(nèi)部類(lèi)方式,可以方便的實(shí)現(xiàn)每個(gè)線程執(zhí)行不同的線程任務(wù)操作。
使用匿名內(nèi)部類(lèi)的方式實(shí)現(xiàn)Runnable接口,重寫(xiě)Runnable接口中的run方法:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("美美" + i);
                }
            }
        },"hello");
        new Thread(thread).start();
        System.out.println(thread.getName());

        Thread.currentThread().setName("美眉");   //設(shè)置main線程名字
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+":妹"+i);
        }

    }
}

3.Callable

有返回值(可選),可以拋出異常

import java.util.concurrent.Callable;

public class MyCallAble implements Callable<String> {

    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            
        }
        return "callable返回了數(shù)據(jù)";
    }
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main2 {
    public static void main(String[] args) {
        MyCallAble callAble = new MyCallAble();
        ExecutorService service = Executors.newFixedThreadPool(5);
        Future<String> submit = service.submit(callAble);
        try {
            System.out.println(submit.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

二、線程安全

線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。

若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無(wú)寫(xiě)操作,一般來(lái)說(shuō),這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作,一般都需要考慮線程同步,否則的話就可能影響線程安全

1.同步代碼塊 synchronized

部分代碼需要加鎖

synchronized(同步鎖){
              需要同步操作的代碼
}

同步鎖:

  1. 鎖對(duì)象 可以是任意類(lèi)型。
  2. 多個(gè)線程對(duì)象 要使用同一把鎖。(需要保證唯一)

2.同步方法

修飾符 synchronized 返回值類(lèi)型 方法名(){
           可能會(huì)產(chǎn)生線程安全問(wèn)題的代碼
}

同步鎖:
對(duì)于非static方法,同步鎖就是this。
對(duì)于static方法,我們使用當(dāng)前方法所在類(lèi)的字節(jié)碼對(duì)象(類(lèi)名.class)

3.鎖機(jī)制(同步鎖/lock鎖)

Lock lock = new ReentrantLock();
lock.lock();

采用Lock,必須主動(dòng)去釋放鎖,并且在發(fā)生異常時(shí),不會(huì)自動(dòng)釋放鎖。因此一般來(lái)說(shuō),

使用Lock必須在try{}catch{}塊中進(jìn)行,并且將釋放鎖的操作放在finally塊中進(jìn)行,以保證鎖一定被被釋放,防止死鎖的發(fā)生

Lock lock = ...;

lock.lock();

try{

    //處理任務(wù)

}catch(Exception ex){

}finally{

    lock.unlock();   //釋放鎖
}

三.線程狀態(tài)

NEW(新建)

線程剛被創(chuàng)建,但是并未啟動(dòng)。還沒(méi)調(diào)用start方法。
Runnable (可運(yùn)行)
線程可以在java虛擬機(jī)中運(yùn)行的狀態(tài),可能正在運(yùn)行自己代碼,也可能沒(méi)有,這取決于操
作系統(tǒng)處理器。
Blocked(鎖阻塞)
當(dāng)一個(gè)線程試圖獲取一個(gè)對(duì)象鎖,而該對(duì)象鎖被其他的線程持有,則該線程進(jìn)入Blocked狀
態(tài);當(dāng)該線程持有鎖時(shí),該線程將變成Runnable狀態(tài)。
Waiting(無(wú)限等待)
一個(gè)線程在等待另一個(gè)線程執(zhí)行一個(gè)(喚醒)動(dòng)作時(shí),該線程進(jìn)入Waiting狀態(tài)。進(jìn)入這個(gè)
狀態(tài)后是不能自動(dòng)喚醒的,必須等待另一個(gè)線程調(diào)用notify或者notifyAll方法才能夠喚醒。
TimedWaiting(計(jì)時(shí)等待)
同waiting狀態(tài),有幾個(gè)方法有超時(shí)參數(shù),調(diào)用他們將進(jìn)入Timed Waiting狀態(tài)。這一狀態(tài)
將一直保持到超時(shí)期滿或者接收到喚醒通知。帶有超時(shí)參數(shù)的常用方法有Thread.sleep 、
Object.wait。
Teminated(被終止)
因?yàn)閞un方法正常退出而死亡,或者因?yàn)闆](méi)有捕獲的異常終止了run方法而死亡

四.線程通信(等待喚醒機(jī)制)

4.1. 2個(gè)線程間使用flag變量實(shí)現(xiàn)交替執(zhí)行

//實(shí)現(xiàn)2個(gè)線程間交替執(zhí)行
public class Demo {
    int flag = 1;


    public synchronized void method1() {
        if (flag != 1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.print("學(xué)");
        System.out.print("習(xí)");
        System.out.print("了");
        System.out.print("嗎");
        System.out.println();
        this.flag = 2;
        this.notify();

    }

    public synchronized void method2() {
        if (flag != 2) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.print("s");
        System.out.print("t");
        System.out.print("u");
        System.out.print("d");
        System.out.print("y");
        System.out.println();
        this.flag = 1;
        this.notify();
    }
}

public class Main {

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    demo.method1();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    demo.method2();
                }
            }
        }).start();
    }
}

4.2 替換if為 whie可實(shí)現(xiàn)3個(gè)線程間有效通信(交替執(zhí)行)

4.3 生產(chǎn)者消費(fèi)者案例

//生產(chǎn)者
public class Producter extends Thread {
    BaoZi bz;

    @Override
    public void run() {
        int num = 0;
        while (true) {
            synchronized (bz) {
                if (bz.flag == true) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("生產(chǎn)者喚醒,開(kāi)始生產(chǎn)");
                if (num % 2 == 0) {
                    bz.pier = "面皮";
                    bz.xianer = "豬肉大蔥";

                } else {
                    bz.xianer = "黑芝麻";
                    bz.pier = "米皮";
                }
System.out.println("包子皮是:" + bz.pier + ";包子餡是:" + bz.xianer);
                num++;
                bz.flag = true;
                bz.notify();
            }
        }
    }
}

//消費(fèi)者
public class Eater extends Thread {
    BaoZi bz;

    @Override
    public void run() {
        while (true) {
            synchronized (bz) {
                if (bz.flag == false) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
System.out.println("吃貨開(kāi)始吃包子,皮是:" + bz.pier + ";餡是:" + bz.xianer);
                bz.flag = false;
                bz.notify();
                System.out.println();
            }
        }
    }
}


public class BaoZi {
    boolean flag = false;
    String pier;
    String xianer;
}


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

        BaoZi baoZi = new BaoZi();

        Producter pt = new Producter();
        pt.bz = baoZi;
        pt.start();
        Eater eater = new Eater();
        eater.bz = baoZi;
        eater.start();
    }
}

五.線程池 ExecutorService(interface)

5.1 合理使用的好處:

  1. 降低資源消耗。
  2. 提高響應(yīng)速度。
  3. 提高線程的可管理性

官方建議使用Executors工程類(lèi)來(lái)創(chuàng)建線程池對(duì)象
阿里巴巴開(kāi)發(fā)手冊(cè)并發(fā)編程:線程池不允許使用 Executors 去創(chuàng)建,而是通過(guò) ThreadPoolExecutor 的方式

Executors 創(chuàng)建線程池的方式
根據(jù)返回的對(duì)象類(lèi)型創(chuàng)建線程池可以分為三類(lèi):
創(chuàng)建返回 ThreadPoolExecutor 對(duì)象
創(chuàng)建返回 ScheduleThreadPoolExecutor 對(duì)象
創(chuàng)建返回 ForkJoinPool 對(duì)象

5.2 使用步驟

  1. 創(chuàng)建線程池對(duì)象。

  2. 創(chuàng)建Runnable接口子類(lèi)對(duì)象。(task)

  3. 提交Runnable接口子類(lèi)對(duì)象。(take task)

    execute 無(wú)返回值

    submit 有返回值

  4. 關(guān)閉線程池(一般不做) shutdown

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(i+" "+Thread.currentThread().getName());
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class Main {
        public static void main(String[] args) {
            ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
       //Runtime.getRuntime().availableProcessors()獲取當(dāng)前cpu核心數(shù)
            MyRunnable runnable = new MyRunnable();
            service.execute(runnable);
            //execute 無(wú)返回值
            Future<?> submit = service.submit(runnable);
            //submit 有返回值
            
        }
    }
    
    
    

六.線程 join

?將線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行 

七.守護(hù)線程 setDaemon

?守護(hù)線程是指在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用的服務(wù)的線程(垃圾回收線程)

?主線程執(zhí)行結(jié)束,守護(hù)線程就結(jié)束

守護(hù)線程設(shè)置在線程啟動(dòng)之前.參數(shù)為true表示是daemon thread.

thread2.setDaemon(true); 
thread2.start();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    小徐andorid閱讀 2,994評(píng)論 3 53
  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書(shū)筆記,整理的知識(shí)點(diǎn),也是為了防止忘記,尊重勞動(dòng)成果,轉(zhuǎn)載注明出處哦!如果你也喜歡,那...
    波波波先森閱讀 11,639評(píng)論 4 56
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽(yáng)閱讀 2,602評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類(lèi) 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,115評(píng)論 1 18
  • 【選摘】 亞當(dāng)斯密 ? “這種本性就是憐憫或同情,就是當(dāng)我們看到或逼真地想象到他人的不幸遭遇時(shí)所產(chǎn)生的感情?!?..
    Cynthiayumoon閱讀 402評(píng)論 0 0

友情鏈接更多精彩內(nèi)容