亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

前言

現(xiàn)在越來(lái)越多的公司,對(duì)精通多線程的的人才越來(lái)越重視,可見(jiàn)多線程技術(shù)有多熱門(mén)。今天,小編結(jié)合清華掃地僧級(jí)別大佬的分享,為大家?guī)?lái)這篇多線程的總結(jié),希望大家能夠喜歡。

清華掃地僧整理的全網(wǎng)最全多線程詳解,看完懷疑自己的認(rèn)知,

 

一、線程概述

幾乎所有的操作系統(tǒng)都支持同時(shí)運(yùn)行多個(gè)任務(wù),一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程。當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程。

二、線程與進(jìn)程

進(jìn)程概述:

幾乎所有的操作系統(tǒng)都支持進(jìn)程的概念,所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程( Process)。當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程。進(jìn)程是處于運(yùn)行過(guò)程中的程序,并且具有一定的獨(dú)立功能,進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。

進(jìn)程特征:

1、獨(dú)立性:進(jìn)程是系統(tǒng)中獨(dú)立存在的實(shí)體,它可以擁有自己獨(dú)立的資源,每一個(gè)進(jìn)程都擁有自己私有的地址空間。在沒(méi)有經(jīng)過(guò)進(jìn)程本身允許的情況下,一個(gè)用戶進(jìn)程不可以直接訪問(wèn)其他進(jìn)程的地址空間

2、動(dòng)態(tài)性:進(jìn)程與程序的區(qū)別在于,程序只是一個(gè)靜態(tài)的指令集合,而進(jìn)程是一個(gè)正在系統(tǒng)中活動(dòng)的指令集合。在進(jìn)程中加入了時(shí)間的概念。進(jìn)程具有自己的生命周期和各種不同的狀態(tài),這些概念在程序中都是不具備的

3、并發(fā)性:多個(gè)進(jìn)程可以在單個(gè)處理器上并發(fā)執(zhí)行,多個(gè)進(jìn)程之間不會(huì)互相影響。

線程:

線程與進(jìn)程相似,但線程是一個(gè)比進(jìn)程更小的執(zhí)行單位。一個(gè)進(jìn)程在其執(zhí)行的過(guò)程中可以產(chǎn)生多個(gè)線程。與進(jìn)程不同的是同類(lèi)的多個(gè)線程共享同一塊內(nèi)存空間和一組系統(tǒng)資源,所以系統(tǒng)在產(chǎn)生一個(gè)線程,或是在各個(gè)線程之間作切換工作時(shí),負(fù)擔(dān)要比進(jìn)程小得多,也正因?yàn)槿绱?,線程也被稱為輕量級(jí)進(jìn)程。

并發(fā)和并行:

并發(fā):同一時(shí)刻只能有一條指令執(zhí)行,但多個(gè)進(jìn)程指令被快速輪換執(zhí)行

并行:同一時(shí)刻,有多條指令在多個(gè)處理器上同時(shí)執(zhí)行  

多線程:

概述:

多線程就是幾乎同時(shí)執(zhí)行多個(gè)線程(一個(gè)處理器在某一個(gè)時(shí)間點(diǎn)上永遠(yuǎn)都只能是一個(gè)線程!即使這個(gè)處理器是多核的,除非有多個(gè)處理器才能實(shí)現(xiàn)多個(gè)線程同時(shí)運(yùn)行。)。幾乎同時(shí)是因?yàn)閷?shí)際上多線程程序中的多個(gè)線程實(shí)際上是一個(gè)線程執(zhí)行一會(huì)然后其他的線程再執(zhí)行,并不是很多書(shū)籍所謂的同時(shí)執(zhí)行。

多線程優(yōu)點(diǎn):

  1. 進(jìn)程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。
  2. 系統(tǒng)創(chuàng)建進(jìn)程時(shí)需要為該進(jìn)程重新分配系統(tǒng)資源,但創(chuàng)建線程則代價(jià)小得多,因此使用多線程來(lái)實(shí)現(xiàn)多任務(wù)并發(fā)比多進(jìn)程的效率高
  3. JAVA語(yǔ)言內(nèi)置了多線程功能支持,而不是單純地作為底層操作系統(tǒng)的調(diào)度方式,從而簡(jiǎn)化了Java的多線程編程

三、使用多線程

多線程的創(chuàng)建:

(1)、繼承Thread類(lèi):

第一步:定義Thread類(lèi)的之類(lèi),并重寫(xiě)run方法,該run方法的方法體就代表了線程需要執(zhí)行的任務(wù)

第二步:創(chuàng)建Thread類(lèi)的實(shí)例

第三步:調(diào)用線程的start()方法來(lái)啟動(dòng)線程

public class FirstThread extends Thread {
     
    private int i;
    public void run() {
        for(;i<100;i++) {
            System.out.println(getName()+" "+i);
        }
    }
     
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            //調(diào)用Thread的currentThread方法獲取當(dāng)前線程
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20) {
                new FirstThread().start();
                new FirstThread().start();
            }
        }
         
    }
 
}

(2)、實(shí)現(xiàn)Runnable接口:

第一步:定義Runnable接口的實(shí)現(xiàn)類(lèi),并重寫(xiě)該接口的run方法,該run方法同樣是線程需要執(zhí)行的任務(wù)

第二步:創(chuàng)建Runnable實(shí)現(xiàn)類(lèi)的實(shí)例,并以此實(shí)例作為T(mén)hread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象

public class SecondThread implements Runnable {
     
    private int i;
 
    @Override
    public void run() {
        for(;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
     
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20) {
                SecondThread s1=new SecondThread();
                new Thread(s1,"新線程1").start();;
                new Thread(s1,"新線程2").start();
            }
        }
    }
     
}

(3)、使用Callable和Future創(chuàng)建線程

細(xì)心的讀者會(huì)發(fā)現(xiàn),上面創(chuàng)建線程的兩種方法。繼承Thread和實(shí)現(xiàn)Runnable接口中的run都是沒(méi)有返回值的。于是從Java5開(kāi)始,Java提供了Callable接口,該接口是Runnable接口的增強(qiáng)版。Callable接口提供了一個(gè)call()方法可以作為線程執(zhí)行體,但call()方法比run()方法功能更強(qiáng)大。

創(chuàng)建并啟動(dòng)有返回值的線程的步驟如下:

第一步:創(chuàng)建 Callable接口的實(shí)現(xiàn)類(lèi),并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,且該call()方法有返回值,再創(chuàng)建 Callable實(shí)現(xiàn)類(lèi)的實(shí)例。從Java8開(kāi)始,可以直接使用 Lambda表達(dá)式創(chuàng)建 Callable對(duì)象

第二步:使用FutureTask類(lèi)來(lái)包裝Callable對(duì)象,該FutureTask對(duì)象封裝了該Callable對(duì)象的call方法的返回值

第三步:使用FutureTask對(duì)象作為T(mén)hread對(duì)象的target創(chuàng)建并啟動(dòng)新線程

第四步:通過(guò)FutureTask的get()方法獲得子線程執(zhí)行結(jié)束后的返回值

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
public class ThirdThread {
     
    public static void main(String[] args) {
        //ThirdThread rt=new ThirdThread();
        FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName()+"的循環(huán)變量i"+i);
            }
            return i;
        }) ;
         
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+"的循環(huán)變量i為"+i);
            if(i==20) {
                new Thread(task,"有返回值的線程").start();;
            }
        }
        try {
            System.out.println("子線程的返回值"+task.get());
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
 
}

創(chuàng)建線程的三種方式的對(duì)比:

采用Runnable、Callable接口的方式創(chuàng)建多線程的優(yōu)缺點(diǎn):

優(yōu)點(diǎn):

1、線程類(lèi)只是實(shí)現(xiàn)了 Runnable接口或 Callable接口,還可以繼承其他類(lèi)

2、在這種方式下,多個(gè)線程可以共享同一個(gè) target對(duì)象,所以非常適合多個(gè)相同線程來(lái)處理同一份資源的情況,從而可以將CPU、代碼和數(shù)據(jù)分開(kāi),形成清晰的模型,較好地體現(xiàn)了面向?qū)ο蟮乃枷搿?/p>

缺點(diǎn):

編程稍稍復(fù)雜,如果需要訪問(wèn)當(dāng)前線程,則必須使用Thread.currentThread()方法。

采用繼承 Thread類(lèi)的方式創(chuàng)建多線程的優(yōu)缺點(diǎn):

優(yōu)點(diǎn):

編寫(xiě)簡(jiǎn)單,如果需要訪問(wèn)當(dāng)前線程,則無(wú)須使用 Thread.current Thread()方法,直接使用this即可獲得當(dāng)前線程

缺點(diǎn):

因?yàn)榫€程已經(jīng)繼承了Thread類(lèi),所以不能再繼承其他類(lèi)

線程的生命周期:

新建和就緒狀態(tài):

當(dāng)程序使用new關(guān)鍵字創(chuàng)建一個(gè)線程后,該線程就處于新建狀態(tài)。

當(dāng)線程對(duì)象調(diào)用了start()方法后,該線程就處于就緒狀態(tài)。

運(yùn)行和阻塞狀態(tài):

如果處于就緒狀態(tài)的線程獲取了CPU,開(kāi)始執(zhí)行run()方法的線程執(zhí)行體,則該線程處于運(yùn)行狀態(tài)。

當(dāng)線程調(diào)用sleep(),調(diào)用一個(gè)阻塞式IO方法,線程會(huì)被阻塞

死亡狀態(tài):

1、run()或者call()方法執(zhí)行完成,線程正常結(jié)束

2、線程拋出一個(gè)未捕獲的Exception或Error

3、直接調(diào)用該線程的stop方法來(lái)結(jié)束該線程——該方法容易導(dǎo)致死鎖,不推薦使用

清華掃地僧整理的全網(wǎng)最全多線程詳解,看完懷疑自己的認(rèn)知,

 

線程狀態(tài)轉(zhuǎn)化圖

四、控制線程

join線程

Thread提供了讓一個(gè)線程等待另一個(gè)線程完成的方法——join方法。當(dāng)在某個(gè)程序執(zhí)行流中調(diào)用其直到被 join方法加入的join線程執(zhí)行完為止

public class JoinThread extends Thread {
     
    //提供一個(gè)有參數(shù)的構(gòu)造器,用于設(shè)置該線程的名字
    public JoinThread(String name) {
        super(name);
    }
     
    //重寫(xiě)run方法,定義線程體
    public void run() {
        for(int i=0;i<10;i++) {
            System.out.println(getName()+" "+i);
        }
    }
     
    public static void main(String[] args) throws InterruptedException {
        //啟動(dòng)子線程
        new JoinThread("新線程").start();
        for(int i=0;i<10;i++) {
            if(i==5) {
                JoinThread jt=new JoinThread("被join的線程");
                jt.start();
                //main線程調(diào)用了jt線程的join方法,main線程
                //必須等jt執(zhí)行結(jié)束才會(huì)向下執(zhí)行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
     
     
 
}

運(yùn)行結(jié)果

main 0
main 1
main 2
main 3
main 4
新線程 0
新線程 1
新線程 2
新線程 3
被join的線程 0
新線程 4
被join的線程 1
新線程 5
被join的線程 2
新線程 6
被join的線程 3
新線程 7
被join的線程 4
新線程 8
被join的線程 5
新線程 9
被join的線程 6
被join的線程 7
被join的線程 8
被join的線程 9
main 5
main 6
main 7
main 8
main 9

后臺(tái)線程:

有一種線程,它是在后臺(tái)運(yùn)行的,它的任務(wù)是為其他的線程提供服務(wù),這種線程被稱為“后臺(tái)線程( Daemon Thread)”,又稱為“守護(hù)線程”或“精靈線程”。JVM的垃圾回收線程就是典型的后臺(tái)線程。

后臺(tái)線程有個(gè)特征:如果所有的前臺(tái)線程都死亡,后臺(tái)線程會(huì)自動(dòng)死亡。

調(diào)用 Thread對(duì)象的 setDaemon(true)方法可將指定線程設(shè)置成后臺(tái)線程。下面程序?qū)?zhí)行線程設(shè)置成后臺(tái)線程,可以看到當(dāng)所有的前臺(tái)線程死亡時(shí),后臺(tái)線程隨之死亡。當(dāng)整個(gè)虛擬機(jī)中只剩下后臺(tái)線程時(shí),程序就沒(méi)有繼續(xù)運(yùn)行的必要了,所以虛擬機(jī)也就退出了。

 public class DaemonThread extends Thread {
 
    //定義后臺(tái)線程的線程體與普通線程沒(méi)有什么區(qū)別
    public void run() {
        for(int i=0;i<1000;i++) {
            System.out.println(getName()+" "+i);
        }
    }
     
    public static void main(String[] args) {
        DaemonThread t=new DaemonThread();
        //將此線程設(shè)置為后臺(tái)線程
        t.setDaemon(true);
        t.start();
        for(int i=0;i<10;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        //程序到此執(zhí)行結(jié)束,前臺(tái)線程(main)結(jié)束,后臺(tái)線程也隨之結(jié)束
    }
     
}

運(yùn)行結(jié)果:

main 0
Thread-0 0
main 1
Thread-0 1
Thread-0 2
main 2
Thread-0 3
Thread-0 4
Thread-0 5
main 3
main 4
Thread-0 6
main 5
Thread-0 7
Thread-0 8
main 6
main 7
main 8
Thread-0 9
main 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21

線程睡眠:

如果需要讓當(dāng)前正在執(zhí)行的線程暫停一段時(shí)間,并進(jìn)入阻塞狀態(tài),則可以通過(guò)調(diào)用 Thread類(lèi)的靜態(tài) sleep方法來(lái)實(shí)現(xiàn)。 sleep方法有兩種重載形式

static void sleep(long millis):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒,并進(jìn)入阻塞狀態(tài)

static void sleep(long millis,int nanos):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒加上nanos毫微秒,并進(jìn)入阻塞狀態(tài),通常我們不會(huì)精確到毫微秒,所以該方法不常用

import java.util.Date;
 
public class SleepTest {
     
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<10;i++) {
            System.out.println("當(dāng)前時(shí)間"+new Date());
            Thread.sleep(1000);
        }
    }
 
}

改變線程優(yōu)先級(jí):

每個(gè)線程執(zhí)行時(shí)都有一定的優(yōu)先級(jí),優(yōu)先級(jí)高的線程獲得較多的執(zhí)行機(jī)會(huì),優(yōu)先級(jí)低的線程則獲得較少的執(zhí)行機(jī)會(huì)。

每個(gè)線程默認(rèn)的優(yōu)先級(jí)都與創(chuàng)建它的父線程的優(yōu)先級(jí)相同,在默認(rèn)情況下,main線程具有普通優(yōu)先級(jí),由main線程創(chuàng)建的子線程也具有普通優(yōu)先級(jí)。

Thread類(lèi)提供了 setPriority(int newPriority)、 getPriority()方法來(lái)設(shè)置和返回指定線程的優(yōu)先級(jí),其中 setPriority()方法的參數(shù)可以是一個(gè)整數(shù),范圍是1-10之間,也可以使用 Thread類(lèi)的如下三個(gè)靜態(tài)常量

MAX_PRIORITY:其值是10

MIN_PRIORITY:其值時(shí)1

NORM_PRIPRITY:其值是5

public class PriorityTest extends Thread {
     
    //定義一個(gè)構(gòu)造器,用于創(chuàng)建線程時(shí)傳入線程的名稱
    public PriorityTest(String name) {
        super(name);
    }
     
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(getName()+",其優(yōu)先級(jí)是:"+getPriority()+"循環(huán)變量的值:"+i);
        }
    }
     
    public static void main(String[] args) {
        //改變主線程的優(yōu)先級(jí)
        Thread.currentThread().setPriority(6);
        for(int i=0;i<30;i++) {
            if(i==10) {
                PriorityTest low=new PriorityTest("低級(jí)");
                low.start();
                System.out.println("創(chuàng)建之初的優(yōu)先級(jí):"+low.getPriority());
                //設(shè)置該線程為最低優(yōu)先級(jí)
                low.setPriority(Thread.MIN_PRIORITY);
            }
            if(i==20) {
                PriorityTest high=new PriorityTest("高級(jí)");
                high.start();
                System.out.println("創(chuàng)建之初的優(yōu)先級(jí)"+high.getPriority());
                high.setPriority(Thread.MAX_PRIORITY);
            }
        }
    }
     
     
 
}

線程同步

線程安全問(wèn)題:

現(xiàn)有如下代碼:

public class Account {
     
    private String accountNo;
    private double balance;
     
    public Account() {}
 
    public Account(String accountNo, double balance) {
        super();
        this.accountNo = accountNo;
        this.balance = balance;
    }
 
    public String getAccountNo() {
        return accountNo;
    }
 
    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }
 
    public double getBalance() {
        return balance;
    }
 
    public void setBalance(double balance) {
        this.balance = balance;
    }
     
    public int hashCode() {
        return accountNo.hashCode();
    }
     
    public boolean equals(Object obj) {
        if(this==obj) {
            return true;
        }
        if(obj!=null&&obj.getClass()==Account.class) {
            Account target=(Account)obj;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
     
}
import com.alibaba.util.Account;
 
public class DrawThread extends Thread{
     
    //模擬用戶賬戶
    private Account account;
     
    //當(dāng)前取錢(qián)線程所希望的錢(qián)數(shù)
    private double drawAmount;
     
    public DrawThread(String name,Account account,double drawAmount) {
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
 
    //多個(gè)線程修改同一個(gè)共享數(shù)據(jù),可能發(fā)生線程安全問(wèn)題
    @Override
    public void run() {
            if(account.getBalance()>drawAmount) {
                System.out.println(getName()+"取錢(qián)成功"+" "+drawAmount);
                try {
                    Thread.sleep(1);
                }catch(Exception e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("t余額為"+" "+account.getBalance());
            }else {
                System.out.println("余額不足,取錢(qián)失敗");
            }
 
    }
     
}
 import com.alibaba.util.Account;
 
public class DrawTest {
     
    public static void main(String[] args) {
        Account account=new Account("1234567",1000);
        //模擬兩個(gè)線程同時(shí)操作賬號(hào)
        new DrawThread("甲", account, 800).start();;
        new DrawThread("乙", account, 800).start();;
    }
 
}

現(xiàn)在我們來(lái)分析一下以上代碼:

我們現(xiàn)在希望實(shí)現(xiàn)的操作是模擬多個(gè)用戶同時(shí)從銀行賬戶里面取錢(qián),如果用戶取錢(qián)數(shù)小于等于當(dāng)前賬戶余額,則提示取款成功,并將余額減去取款錢(qián)數(shù),如果余額不足,則提示余額不足,取款失敗。

Account 類(lèi):銀行賬戶類(lèi),里面有一些賬戶的基本信息,以及操作賬戶信息的方法

DrawThread類(lèi):繼承了Thread,是一個(gè)多線程類(lèi),用于模擬多個(gè)用戶操作同一個(gè)賬戶的信息

DrawTest:測(cè)試類(lèi)

這時(shí)我們運(yùn)行程序可能會(huì)看到如下運(yùn)行結(jié)果:

甲取錢(qián)成功 800.0
乙取錢(qián)成功 800.0
    余額為 200.0
    余額為 -600.0

如何解決線程安全問(wèn)題:

①、同步代碼塊:

為了解決線程問(wèn)題,Java的多線程支持引入了同步監(jiān)視器來(lái)解決這個(gè)問(wèn)題,使用同步監(jiān)視器的通用方法就是同步代碼塊。同步代碼塊的語(yǔ)法格式如下:

synchronized(obj){

  //此處的代碼就是同步代碼塊

}

我們將上面銀行中DrawThread類(lèi)作如下修改:

import com.alibaba.util.Account;
 
public class DrawThread extends Thread{
     
    //模擬用戶賬戶
    private Account account;
     
    //當(dāng)前取錢(qián)線程所希望的錢(qián)數(shù)
    private double drawAmount;
     
    public DrawThread(String name,Account account,double drawAmount) {
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
 
    //多個(gè)線程修改同一個(gè)共享數(shù)據(jù),可能發(fā)生線程安全問(wèn)題
    @Override
    public void run() {
        //使用account作為同步監(jiān)視器,任何線程在進(jìn)入下面同步代碼塊之前
        //必須先獲得account賬戶的鎖定,其他線程無(wú)法獲得鎖,也就無(wú)法修改它
        //這種做法符合:"加鎖-修改-釋放鎖"的邏輯
        synchronized(account) {
            if(account.getBalance()>drawAmount) {
                System.out.println(getName()+"取錢(qián)成功"+" "+drawAmount);
                try {
                    Thread.sleep(1);
                }catch(Exception e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("t余額為"+" "+account.getBalance());
            }else {
                System.out.println("余額不足,取錢(qián)失敗");
            }
        }
         
         
    }
     
}

我們來(lái)看這次的運(yùn)行結(jié)果:

甲取錢(qián)成功 800.0
    余額為 200.0
余額不足,取錢(qián)失敗

我們發(fā)現(xiàn)結(jié)果變了,是我們希望看到的結(jié)果。因?yàn)槲覀冊(cè)诳赡馨l(fā)生線程安全問(wèn)題的地方加上了synchronized代碼塊

②:同步方法:

與同步代碼塊對(duì)應(yīng),Java的多線程安全支持還提供了同步方法,同步方法就是使用 synchronized關(guān)鍵字來(lái)修飾某個(gè)方法,則該方法稱為同步方法。對(duì)于 synchronized修飾的實(shí)例方法(非 static方法)而言,無(wú)須顯式指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,也就是調(diào)用該方法的對(duì)象。同步方法語(yǔ)法格式如下:

public synchronized void 方法名(){

  //具體代碼

}

③、同步鎖:

從Java5開(kāi)始,Java提供了一種功能更強(qiáng)大的線程同步機(jī)制—一通過(guò)顯式定義同步鎖對(duì)象來(lái)實(shí)現(xiàn)同步,在這種機(jī)制下,同步鎖由Lock對(duì)象充當(dāng)。

Lock提供了比 synchronized方法和 synchronized代碼塊更廣泛的鎖定操作,Lock允許實(shí)現(xiàn)更靈活的結(jié)構(gòu),可以具有差別很大的屬性,并且支持多個(gè)相關(guān)的 Condition對(duì)象。

在實(shí)現(xiàn)線程安全的控制中,比較常用的是 ReentrantLock(可重入鎖)。使用該Lock對(duì)象可以顯式加鎖、釋放鎖,通常使用ReentrantLock的代碼格式如下:

class X{
    //定義鎖對(duì)象
    private final ReentrantLock lock=new ReentrantLock();
    //...
     
    //定義需要保護(hù)線程安全的方法
    public void m() {
        //加鎖
        lock.lock();
        try {
            //需要保證線程安全的代碼
            //...method body
        }finally {
            //釋放鎖
            lock.unlock();
        }
    }
     
}

死鎖:

當(dāng)兩個(gè)線程相互等待對(duì)方釋放同步監(jiān)視器時(shí)就會(huì)發(fā)生死鎖,Java虛擬機(jī)沒(méi)有監(jiān)測(cè),也沒(méi)有采取措施來(lái)處理死鎖情況,所以多線程編程時(shí)應(yīng)該采取措施避免死鎖岀現(xiàn)。一旦岀現(xiàn)死鎖,整個(gè)程序既不會(huì)發(fā)生任何異常,也不會(huì)給出任何提示,只是所有線程處于阻塞狀態(tài),無(wú)法繼續(xù)。

死鎖是很容易發(fā)生的,尤其在系統(tǒng)中出現(xiàn)多個(gè)同步監(jiān)視器的情況下,如下程序?qū)?huì)出現(xiàn)死鎖

class A{
    public synchronized void foo(B b) {
        System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"進(jìn)入A實(shí)例的foo方法");//①
         
        try {
            Thread.sleep(200);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"企圖調(diào)用B的方法");//③
        b.last();
    }
     
    public synchronized void last() {
        System.out.println("進(jìn)入了A類(lèi)的last方法");
    }
}
 
 
class B{
     
    public synchronized void bar(A a) {
        System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"進(jìn)入B實(shí)例的bar方法");//②
        try {
            Thread.sleep(200);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("當(dāng)前線程名:"+Thread.currentThread().getName()+"企圖調(diào)用A的方法");//④
        a.last();
    }
     
    public synchronized void last() {
        System.out.println("進(jìn)入了B類(lèi)的last方法");
    }
     
}
 
public class DeadLock implements Runnable {
     
    A a=new A();
    B b=new B();
     
    public void init() {
        Thread.currentThread().setName("主線程");
        a.foo(b);
        System.out.println("進(jìn)入了主線程之后");
    }
     
     
     
    @Override
    public void run() {
        Thread.currentThread().setName("副線程");
        b.bar(a);
        System.out.println("進(jìn)入副線程之后");
    }
     
     
    public static void main(String[] args) {
        DeadLock d=new DeadLock();
        new Thread(d).start();
        d.init();
    }
     
     
}

運(yùn)行結(jié)果:

清華掃地僧整理的全網(wǎng)最全多線程詳解,看完懷疑自己的認(rèn)知,

 

從圖中可以看出,程序既無(wú)法向下執(zhí)行,也不會(huì)拋出任何異常,就一直“僵持”著。究其原因,是因?yàn)椋荷厦娉绦蛑蠥對(duì)象和B對(duì)象的方法都是同步方法,也就是A對(duì)象和B對(duì)象都是同步鎖。程序中兩個(gè)線程執(zhí)行,副線程的線程執(zhí)行體是 DeadLock類(lèi)的run()方法,主線程的線程執(zhí)行體是 Deadlock的main()方法(主線程調(diào)用了init()方法)。其中run()方法中讓B對(duì)象調(diào)用b進(jìn)入foo()方法之前,該線程對(duì)A對(duì)象加鎖—當(dāng)程序執(zhí)行到①號(hào)代碼時(shí),主線程暫停200ms:CPU切換到執(zhí)行另一個(gè)線程,讓B對(duì)象執(zhí)行bar()方法,所以看到副線程開(kāi)始執(zhí)行B實(shí)例的bar()方法,進(jìn)入bar()方法之前,該線程對(duì)B對(duì)象加鎖——當(dāng)程序執(zhí)行到②號(hào)代碼時(shí),副線程也暫停200ms:接下來(lái)主線程會(huì)先醒過(guò)來(lái),繼續(xù)向下執(zhí)行,直到③號(hào)代碼處希望調(diào)用B對(duì)象的last()方法——執(zhí)行該方法之前必須先對(duì)B對(duì)象加鎖,但此時(shí)副線程正保持著B(niǎo)對(duì)象的鎖,所以主線程阻塞;接下來(lái)副線程應(yīng)該也醒過(guò)來(lái)了,繼續(xù)向下執(zhí)行,直到④號(hào)代碼處希望調(diào)用A對(duì)象的 last()方法——執(zhí)行該方法之前必須先對(duì)A對(duì)象加鎖,但此時(shí)主線程沒(méi)有釋放對(duì)A對(duì)象的鎖——至此,就出現(xiàn)了主線程保持著A對(duì)象的鎖,等待對(duì)B對(duì)象加鎖,而副線程保持著B(niǎo)對(duì)象的鎖,等待對(duì)A對(duì)象加鎖,兩個(gè)線程互相等待對(duì)方先釋放,所以就出現(xiàn)了死鎖。

六、線程池

系統(tǒng)啟動(dòng)一個(gè)新線程的成本是比較高的,因?yàn)樗婕芭c操作系統(tǒng)交互。在這種情形下,使用線程池可以很好地提高性能,尤其是當(dāng)程序中需要?jiǎng)?chuàng)建大量生存期很短暫的線程時(shí),更應(yīng)該考慮使用線程池。

與數(shù)據(jù)庫(kù)連接池類(lèi)似的是,線程池在系統(tǒng)啟動(dòng)時(shí)即創(chuàng)建大量空閑的線程,程序?qū)⒁粋€(gè) Runnable對(duì)象或 Callable對(duì)象傳給線程池,線程池就會(huì)啟動(dòng)一個(gè)空閑的線程來(lái)執(zhí)行它們的run()或call()方法,當(dāng)run()或call()方法執(zhí)行結(jié)束后,該線程并不會(huì)死亡,而是再次返回線程池中成為空閑狀態(tài),等待執(zhí)行下一個(gè)Runnable對(duì)象的run()或call()方法。

創(chuàng)建線程池的幾個(gè)常用的方法:

1.newSingleThreadExecutor創(chuàng)建一個(gè)單線程的線程池。這個(gè)線程池只有一個(gè)線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)。如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束,那么會(huì)有一個(gè)新的線程來(lái)替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。

2.newFixedThreadPool創(chuàng)建固定大小的線程池。每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小。線程池的大小一旦達(dá)到最大值就會(huì)保持不變,如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束,那么線程池會(huì)補(bǔ)充一個(gè)新線程。

3.newCachedThreadPool創(chuàng)建一個(gè)可緩存的線程池。如果線程池的大小超過(guò)了處理任務(wù)所需要的線程,

那么就會(huì)回收部分空閑(60秒不執(zhí)行任務(wù))的線程,當(dāng)任務(wù)數(shù)增加時(shí),此線程池又可以智能的添加新線程來(lái)處理任務(wù)。此線程池不會(huì)對(duì)線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說(shuō)JVM)能夠創(chuàng)建的最大線程大小。

4.newScheduledThreadPool創(chuàng)建一個(gè)大小無(wú)限的線程池。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadPoolTest {
     
    public static void main(String[] args) {
        ExecutorService pool=Executors.newFixedThreadPool(6);
        Runnable target=()->{
            for(int i=0;i<10;i++) {
                System.out.println(Thread.currentThread().getName()+"的i的值"+i);
            }
        };
        pool.submit(target);
        pool.submit(target);
        pool.submit(target);
        //關(guān)閉線程池
        pool.shutdown();
         
    }
 
}

運(yùn)行結(jié)果:

pool-1-thread-1的i的值0
pool-1-thread-2的i的值0
pool-1-thread-3的i的值0
pool-1-thread-2的i的值1
pool-1-thread-1的i的值1
pool-1-thread-2的i的值2
pool-1-thread-3的i的值1
pool-1-thread-2的i的值3
pool-1-thread-1的i的值2
pool-1-thread-2的i的值4
pool-1-thread-3的i的值2
pool-1-thread-2的i的值5
pool-1-thread-1的i的值3
pool-1-thread-2的i的值6
pool-1-thread-3的i的值3
pool-1-thread-2的i的值7
pool-1-thread-1的i的值4
pool-1-thread-2的i的值8
pool-1-thread-3的i的值4
pool-1-thread-2的i的值9
pool-1-thread-1的i的值5
pool-1-thread-3的i的值5
pool-1-thread-1的i的值6
pool-1-thread-1的i的值7
pool-1-thread-1的i的值8
pool-1-thread-1的i的值9
pool-1-thread-3的i的值6
pool-1-thread-3的i的值7
pool-1-thread-3的i的值8
pool-1-thread-3的i的值9

后記

怎么樣?來(lái)自清華掃地僧的功力如此深厚,想了解是哪位嗎?那就請(qǐng)多多留言評(píng)論,或者關(guān)注小編,私信回復(fù)【學(xué)習(xí)】,即可免費(fèi)獲贈(zèng)清華大佬海量學(xué)習(xí)資料。

心動(dòng)就趕緊行動(dòng)起來(lái)吧~~~

分享到:
標(biāo)簽:多線程
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定