Java多線程(一)

Java多線程(一)

1. 并發(fā)與并行

并行:指兩個(gè)或多個(gè)事件在同一時(shí)刻發(fā)生(同時(shí)發(fā)生)砖第。

并發(fā):指兩個(gè)或多個(gè)事件在同一個(gè)時(shí)間段內(nèi)發(fā)生。

多線程.png

2. 進(jìn)程素征、線程

進(jìn)程

  • 進(jìn)程是正在運(yùn)行的程序的實(shí)例集嵌。
  • 進(jìn)程是線程的容器,即一個(gè)進(jìn)程中可以開(kāi)啟多個(gè)線程御毅。
  • 比如打開(kāi)一個(gè)瀏覽器根欧、打開(kāi)一個(gè)word等操作,都會(huì)創(chuàng)建進(jìn)程端蛆。

線程

  • 線程是進(jìn)程內(nèi)部的一個(gè)獨(dú)立執(zhí)行單元凤粗;
  • 一個(gè)進(jìn)程可以同時(shí)并發(fā)運(yùn)行多個(gè)線程;
  • 比如進(jìn)程可以理解為醫(yī)院今豆,線程是掛號(hào)嫌拣、就診柔袁、繳費(fèi)、拿藥等業(yè)務(wù)活動(dòng)

多線程

多個(gè)線程并發(fā)執(zhí)行异逐。

3. 線程創(chuàng)建

Java中線程有四種創(chuàng)建方式:

  • 繼承Thread類(lèi)
  • 實(shí)現(xiàn)Runnable接口
  • 實(shí)現(xiàn)Callable接口
  • 線程池

3.1. 繼承Thread類(lèi)

第一步:創(chuàng)建自定義線程類(lèi)

package cn.edu.nwafu;
import java.util.Date;
/**
 * @author shensr
 * @version V1.0
 * @description: 繼承Thread類(lèi) 創(chuàng)建線程
 * @create 2019/9/14
 **/
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            //getTime() 獲取時(shí)間的毫秒數(shù)
            System.out.println("myThread執(zhí)行的時(shí)間:"+new Date().getTime());
        }
    }
}

第二步:創(chuàng)建測(cè)試類(lèi)

package cn.edu.nwafu;


import java.util.Date;

/**
 * @author shensr
 * @version V1.0
 * @description: 測(cè)試類(lèi)
 * @create 2019/9/14
 **/


public class MyThreadTest {
    public static void main(String[] args) {
        //1.創(chuàng)建自定義線程類(lèi)實(shí)例
        MyThread myThread = new MyThread();
        //2.啟動(dòng)線程
        myThread.start();
        //3.在main主線程中打印信息
        for (int i = 0; i < 10; i++) {
            System.out.println("主線程執(zhí)行時(shí)間:" + new Date().getTime());
        }
    }

}

執(zhí)行結(jié)果如下(不唯一)

myThread執(zhí)行的時(shí)間:1568444953164
主線程執(zhí)行時(shí)間:1568444953164
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953166
主線程執(zhí)行時(shí)間:1568444953166
主線程執(zhí)行時(shí)間:1568444953166

3.2 實(shí)現(xiàn)Runnable接口

第一步:創(chuàng)建自定義線程類(lèi)

package cn.edu.nwafu;

import java.util.Date;

/**
 * @author shensr
 * @version V1.0
 * @description: 自定義類(lèi) 實(shí)現(xiàn)Runnable接口
 * @create 2019/9/14
 **/

public class MyRunnable implements  Runnable {
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:"+new Date().getTime()+"執(zhí)行次數(shù):"+i);
        }
    }

}

第二步:創(chuàng)建測(cè)試類(lèi)

package cn.edu.nwafu;


import java.util.Date;

/**
 * @author shensr
 * @version V1.0
 * @description: 測(cè)試 MyRunnable類(lèi)
 * @create 2019/9/14
 **/


public class MyRunnableTest {

    public static void main(String[] args) {
        //一捶索、實(shí)現(xiàn)Runnable接口
        //1.通過(guò)Thread類(lèi)執(zhí)行Runnable類(lèi)
        Thread thread = new Thread(new MyRunnable());
        //2.啟動(dòng)線程
        thread.setName("MyRunnable");
        thread.start();
        //3.在main主線程中打印信息
        Thread.currentThread().setName("主線程");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:" + new Date().getTime()+"執(zhí)行次數(shù):"+i);
        }
    }
}

執(zhí)行結(jié)果如下(不唯一)

MyRunnable執(zhí)行時(shí)間:1568448360595執(zhí)行次數(shù):0
主線程執(zhí)行時(shí)間:1568448360595執(zhí)行次數(shù):0
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):1
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):2
MyRunnable執(zhí)行時(shí)間:1568448360595執(zhí)行次數(shù):1
MyRunnable執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):2
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):3
MyRunnable執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):3
MyRunnable執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):4
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):4

3.3 實(shí)現(xiàn)Callable接口

Callable需要使用FutureTask類(lèi)幫助執(zhí)行,F(xiàn)utureTask類(lèi)結(jié)構(gòu)如下:

FutureTask.png

Future接口:

  • 判斷任務(wù)是否完成:isDone()
  • 能夠中斷任務(wù):cancel()
  • 能夠獲取任務(wù)執(zhí)行結(jié)果:get()

第一步:創(chuàng)建自定義線程類(lèi)

package cn.edu.nwafu;

import java.util.Date;
import java.util.concurrent.Callable;

/**
 * @author shensr
 * @version V1.0
 * @description:
 * @create 2019/9/14
 **/

public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:"+new Date().getTime()+"執(zhí)行次數(shù):"+i);
        }
        return "MyCallable執(zhí)行完成灰瞻!";
    }
}

第二步:創(chuàng)建測(cè)試類(lèi)

package cn.edu.nwafu;

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author shensr
 * @version V1.0
 * @description:
 * @create 2019/9/14
 **/


public class MyCallableTest {

    public static void main(String[] args) {
        //3情组、實(shí)現(xiàn)Runnable接口
        //3.1.創(chuàng)建FutureTask實(shí)例,創(chuàng)建MyCallable實(shí)例
        FutureTask task = new FutureTask( new MyCallable());
        //3.2. 創(chuàng)建Thread實(shí)例箩祥,執(zhí)行創(chuàng)建FutureTask
        Thread thread = new Thread(task,"MyRunnable");
        thread.start();
        //3.3.在main主線程中打印信息
        Thread.currentThread().setName("主線程");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:" + new Date().getTime()+"執(zhí)行次數(shù):"+i);
        }

        //3.4. 獲取并打印MyCallable返回結(jié)果
        try {
            System.out.println("MyCallable返回結(jié)果:"+task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果如下(不唯一)

主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):0
MyRunnable執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):0
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):1
MyRunnable執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):1
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):2
MyRunnable執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):2
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):3
MyRunnable執(zhí)行時(shí)間:1568449491986執(zhí)行次數(shù):3
主線程執(zhí)行時(shí)間:1568449491986執(zhí)行次數(shù):4
MyRunnable執(zhí)行時(shí)間:1568449491986執(zhí)行次數(shù):4
MyCallable返回結(jié)果:MyCallable執(zhí)行完成院崇!

3.4 線程池-Executor

線程池線類(lèi)關(guān)系圖

Executor.png

Executor接口:

聲明了execute(Runnable runnable)方法,執(zhí)行任務(wù)代碼

ExecutorService接口:

繼承Executor接口袍祖,聲明方法:submit底瓣、invokeAll、invokeAny以及shutDown等

AbstractExecutorService抽象類(lèi):

實(shí)現(xiàn)ExecutorService接口蕉陋,基本實(shí)現(xiàn)ExecutorService中聲明的所有方法

ScheduledExecutorService接口:

繼承ExecutorService接口捐凭,聲明定時(shí)執(zhí)行任務(wù)方法

ThreadPoolExecutor類(lèi):

繼承類(lèi)AbstractExecutorService,實(shí)現(xiàn)execute凳鬓、submit茁肠、shutdown、shutdownNow方法

ScheduledThreadPoolExecutor類(lèi):

繼承ThreadPoolExecutor類(lèi)缩举,實(shí)現(xiàn)ScheduledExecutorService接口并實(shí)現(xiàn)其中的方法

Executors類(lèi):

提供快速創(chuàng)建線程池的方法

第一步:創(chuàng)建自定義類(lèi)實(shí)現(xiàn)Runnable接口

package cn.edu.nwafu;

import java.util.Date;

/**
 * @author shensr
 * @version V1.0
 * @description: 自定義類(lèi) 實(shí)現(xiàn)Runnable接口
 * @create 2019/9/14
 **/

public class MyRunnable implements  Runnable {
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:"+new Date().getTime()+"執(zhí)行次數(shù):"+i);
        }
    }

}

第二步:創(chuàng)建測(cè)試類(lèi)

package cn.edu.nwafu;

import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author shensr
 * @version V1.0
 * @description: 使用線程池創(chuàng)建線程
 * @create 2019/9/14
 **/

public class ThreadExecutor {
    public static void main(String[] args) {
        //4. 使用線程池創(chuàng)建線程
        //4.1 使用Executors獲取線程池對(duì)象
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //4.2 通過(guò)線程池對(duì)象獲取線程并執(zhí)行MyRunnable
        executorService.execute(new MyRunnable());
        //4.3 主線程打印信息
        for (int i = 0; i < 5; i++) {
            System.out.println("主線程執(zhí)行時(shí)間:" + new Date().getTime());
        }
    }

}

4. 小結(jié)

4.1 實(shí)現(xiàn)接口和繼承Thread類(lèi)比較

  • 接口更適合多個(gè)相同的程序代碼的線程去共享同一個(gè)資源垦梆。
  • 接口可以避免java中的單繼承的局限性。
  • 接口代碼可以被多個(gè)線程共享仅孩,代碼和線程獨(dú)立托猩。
  • 線程池只能放入實(shí)現(xiàn)Runable或Callable接口的線程,不能直接放入繼承Thread的類(lèi)辽慕。
  • 擴(kuò)充:在java中京腥,每次程序運(yùn)行至少啟動(dòng)2個(gè)線程。一個(gè)是main線程溅蛉,一個(gè)是垃圾收集線程公浪。

4.2 Runnable和Callable接口比較

相同點(diǎn):

  • 兩者都是接口;
  • 兩者都可用來(lái)編寫(xiě)多線程程序船侧;
  • 兩者都需要調(diào)用Thread.start()啟動(dòng)線程欠气;

不同點(diǎn):

  • 實(shí)現(xiàn)Callable接口的線程能返回執(zhí)行結(jié)果;而實(shí)現(xiàn)Runnable接口的線程不能返回結(jié)果勺爱;
  • Callable接口的call()方法允許拋出異常晃琳;而Runnable接口的run()方法的不允許拋異常;
  • 實(shí)現(xiàn)Callable接口的線程可以調(diào)用Future.cancel取消執(zhí)行 琐鲁,而實(shí)現(xiàn)Runnable接口的線程不能

注意點(diǎn):

  • Callable接口支持返回執(zhí)行結(jié)果卫旱,此時(shí)需要調(diào)用FutureTask.get()方法實(shí)現(xiàn),此方法會(huì)阻塞主線程直到獲取‘將來(lái)’結(jié)果围段;當(dāng)不調(diào)用此方法時(shí)顾翼,主線程不會(huì)阻塞!

5. 線程生命周期

?
線程生命周期.png

5.1. 新建

  • new關(guān)鍵字創(chuàng)建了一個(gè)線程之后奈泪,該線程就處于新建狀態(tài)
  • JVM為線程分配內(nèi)存适贸,初始化成員變量值

5.2. 就緒

  • 當(dāng)線程對(duì)象調(diào)用了start()方法之后,該線程處于就緒狀態(tài)
  • JVM為線程創(chuàng)建方法棧和程序計(jì)數(shù)器涝桅,等待線程調(diào)度器調(diào)度

5.3. 運(yùn)行

  • 就緒狀態(tài)的線程獲得CPU資源拜姿,開(kāi)始運(yùn)行run()方法,該線程進(jìn)入運(yùn)行狀態(tài)

5.4. 阻塞

當(dāng)發(fā)生如下情況時(shí)冯遂,線程將會(huì)進(jìn)入阻塞狀態(tài)

  • 線程調(diào)用sleep()方法主動(dòng)放棄所占用的處理器資源
  • 線程調(diào)用了一個(gè)阻塞式IO方法蕊肥,在該方法返回之前,該線程被阻塞
  • 線程試圖獲得一個(gè)同步鎖(同步監(jiān)視器)蛤肌,但該同步鎖正被其他線程所持有壁却。
  • 線程在等待某個(gè)通知(notify)
  • 程序調(diào)用了線程的suspend()方法將該線程掛起。但這個(gè)方法容易導(dǎo)致死鎖裸准,所以應(yīng)該盡量避免使用該方法

5.5. 死亡

線程會(huì)以如下3種方式結(jié)束展东,結(jié)束后就處于死亡狀態(tài):

  • run()或call()方法執(zhí)行完成,線程正常結(jié)束炒俱。
  • 線程拋出一個(gè)未捕獲的Exception或Error盐肃。
  • 調(diào)用該線程stop()方法來(lái)結(jié)束該線程,該方法容易導(dǎo)致死鎖权悟,不推薦使用恼蓬。

6. 線程安全問(wèn)題

6.1. 什么是線程安全

如果有多個(gè)線程同時(shí)運(yùn)行同一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi),程序每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的僵芹,而且其他的變量的值也和預(yù)期的是一樣的处硬,就是線程安全的;反之拇派,則是線程不安全的荷辕。

6.2. 問(wèn)題演示

為了演示線程安全問(wèn)題,我們采用多線程模擬多個(gè)窗口同時(shí)售賣(mài)《哪吒之魔童降世》電影票件豌。

6.2.1. 第一步:創(chuàng)建售票線程類(lèi)

package cn.edu.nwafu.safe;

public class Ticket implements Runnable {
    private int ticktNum = 100;
    
    public void run() {
        while(true){
            if(ticktNum > 0){
                //1.模擬出票時(shí)間
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //2.打印進(jìn)程號(hào)和票號(hào)疮方,票數(shù)減1
                String name = Thread.currentThread().getName();
                System.out.println("線程"+name+"售票:"+ticktNum--);
            }
        }
    }
}

6.2.2. 第二步:創(chuàng)建測(cè)試類(lèi)

package cn.edu.nwafu.safe;

import cn.edu.nwafu.safe.Ticket;

public class TicketDemo {
    public static void main(String[] args){
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket, "窗口1");
        Thread thread2 = new Thread(ticket, "窗口2");
        Thread thread3 = new Thread(ticket, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

6.3. 問(wèn)題分析

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

若每個(gè)線程對(duì)全局變量茧彤、靜態(tài)變量只讀骡显,不寫(xiě),一般來(lái)說(shuō),這個(gè)變量是線程安全的惫谤;

若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作壁顶,一般都需要考慮線程同步,否則的話就可能影響線程安全溜歪。

綜上所述若专,線程安全問(wèn)題根本原因:

  • 多個(gè)線程在操作共享的數(shù)據(jù)
  • 操作共享數(shù)據(jù)的線程代碼有多條蝴猪;
  • 多個(gè)線程對(duì)共享數(shù)據(jù)有寫(xiě)操作调衰;

6.4. 問(wèn)題解決-線程同步

要解決以上線程問(wèn)題,只要在某個(gè)線程修改共享資源的時(shí)候自阱,其他線程不能修改該資源嚎莉,等待修改完畢同步之后,才能去搶奪CPU資源沛豌,完成對(duì)應(yīng)的操作趋箩,保證了數(shù)據(jù)的同步性,解決了線程不安全的現(xiàn)象琼懊。

為了保證每個(gè)線程都能正常執(zhí)行共享資源操作,Java引入了7種線程同步機(jī)制阁簸。

  1.     同步代碼塊(synchronized)
    
  2.     同步方法(synchronized)
    
  3.     同步鎖(ReenreantLock)
    
  4.     特殊域變量(volatile)
    
  5.     局部變量(ThreadLocal)
    
  6.     阻塞隊(duì)列(LinkedBlockingQueue)
    
  7.     原子變量(Atomic*)
    

6.4.1. 同步代碼塊(synchronized)

同步代碼塊 :

synchronized 關(guān)鍵字可以用于方法中的某個(gè)區(qū)塊中,表示只對(duì)這個(gè)區(qū)塊的資源實(shí)行互斥訪問(wèn)哼丈。

語(yǔ)法:

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

同步鎖:

對(duì)象的同步鎖只是一個(gè)概念,可以想象為在對(duì)象上標(biāo)記了一個(gè)鎖.

  • 鎖對(duì)象可以是任意類(lèi)型启妹。
  • 多個(gè)線程要使用同一把鎖。

注意:在任何時(shí)候,最多允許一個(gè)線程擁有同步鎖,誰(shuí)拿到鎖就進(jìn)入代碼塊,其他的線程只能在外等著(BLOCKED)醉旦。

使用同步代碼塊代碼如下:

package cn.edu.nwafu.safe;

public class Ticket implements Runnable {
    private int ticktNum = 100;

    //定義鎖對(duì)象
    Object obj = new Object();

    public void run() {
        while(true){
            synchronized (obj){
                if(ticktNum > 0){
                    //1.模擬出票時(shí)間
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //2.打印進(jìn)程號(hào)和票號(hào)饶米,票數(shù)減1
                    String name = Thread.currentThread().getName();
                    System.out.println("線程"+name+"售票:"+ticktNum--);
                }
            }
        }
    }
}

6.4.2. 同步方法(synchronized)

同步方法:

使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時(shí)候,其他線程只能在方法外等著。

格式:

public synchronized void method(){
    //TODO 可能會(huì)產(chǎn)生線程安全問(wèn)題的代碼 
}

同步鎖是誰(shuí)?

  • 對(duì)于非static方法,同步鎖就是this车胡。
  • 對(duì)于static方法,同步鎖是當(dāng)前方法所在類(lèi)的字節(jié)碼對(duì)象(類(lèi)名.class)檬输。

使用同步方法代碼如下:

package cn.edu.nwafu.safe;

/**
 * @author shensr
 * @version V1.0
 * @description: 電影票對(duì)象
 * @create 2019/9/15
 **/

public class Ticket implements Runnable {

    private int ticketNum = 50;//電影票數(shù)量
    private Object obj = new Object(); //鎖對(duì)象,可以理解為鑰匙匈棘,拿到鑰匙可以執(zhí)行代碼
    //同步方法實(shí)現(xiàn)
    public void run() {
        while (true) {
            safeTicket();
        }
    }

    /**
     * 同步方法實(shí)現(xiàn)
     * 注意 :對(duì)于static方法,同步鎖是當(dāng)前方法所在類(lèi)的字節(jié)碼對(duì)象(類(lèi)名.class)丧慈。
     * 對(duì)于非static方法,同步鎖就是this。
     */
    private synchronized void safeTicket() {
        if(ticketNum>0){
                    //郵票主卫,線程睡眠1000ms逃默,售票
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("線程"+Thread.currentThread().getName()+"售票:"+ticketNum--);
                }
    }
}

6.4.3. 同步鎖(ReenreantLock)

同步鎖:

java.util.concurrent.locks.Lock 機(jī)制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>

同步鎖方法:

public void lock() :加同步鎖簇搅。

public void unlock() :釋放同步鎖完域。

使用重入鎖代碼如下:

package cn.edu.nwafu.safe;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author shensr
 * @version V1.0
 * @description: 電影票對(duì)象
 * @create 2019/9/15
 **/

public class Ticket implements Runnable {
    //3. 同步鎖實(shí)現(xiàn)
    private int ticketNum = 50;//電影票數(shù)量
    //定義鎖對(duì)象(重入鎖):構(gòu)造函數(shù)參數(shù)為線程是否公平獲取鎖true-公平;
    //           false-不公平瘩将,即由某個(gè)線程獨(dú)占吟税,默認(rèn)是false
    private Lock lock = new ReentrantLock(true);

    public void run() {
        while (true) {
            lock.lock();//加鎖
            try {
                if (ticketNum > 0) {
                    //郵票凹耙,線程睡眠1000ms,售票
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("線程" + Thread.currentThread().getName() + "售票:" + ticketNum--);
                }
            }catch (Exception e){

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

        }
    }
}

注意:一定要記得釋放鎖肠仪,否則會(huì)引發(fā)死鎖肖抱。

6.5. 小結(jié)

Synchronized和Lock區(qū)別

  • synchronized是java內(nèi)置關(guān)鍵字,在jvm層面藤韵,Lock是個(gè)java類(lèi)虐沥;
  • synchronized無(wú)法判斷是否獲取鎖的狀態(tài)熊经,Lock可以判斷是否獲取到鎖泽艘;
  • synchronized會(huì)自動(dòng)釋放鎖(a 線程執(zhí)行完同步代碼會(huì)釋放鎖 ;b 線程執(zhí)行過(guò)程中發(fā)生異常會(huì)釋放鎖)镐依,Lock需在finally中手工釋放鎖(unlock()方法釋放鎖)匹涮,否則容易造成線程死鎖;
  • 用synchronized關(guān)鍵字的兩個(gè)線程1和線程2槐壳,如果當(dāng)前線程1獲得鎖然低,線程2線程等待。如果線程1阻塞务唐,線程2則會(huì)一直等待下去雳攘,而Lock鎖就不一定會(huì)等待下去,如果嘗試獲取不到鎖枫笛,線程可以不用一直等待就結(jié)束了吨灭;
  • synchronized的鎖可重入(拿到鎖之后還可以再次申請(qǐng))、不可中斷刑巧、非公平喧兄,而Lock鎖可重入、可判斷啊楚、可公平(兩者皆可)
  • Lock鎖適合大量同步的代碼的同步問(wèn)題吠冤,synchronized鎖適合代碼少量的同步問(wèn)題。

7. 線程死鎖

7.1. 什么是死鎖

多線程以及多進(jìn)程改善了系統(tǒng)資源的利用率并提高了系統(tǒng)的處理能力恭理。然而拯辙,并發(fā)執(zhí)行也帶來(lái)了新的問(wèn)題--死鎖。

所謂死鎖是指多個(gè)線程因競(jìng)爭(zhēng)資源而造成的一種僵局(互相等待)颜价,若無(wú)外力作用涯保,這些進(jìn)程都將無(wú)法向前推進(jìn)。

死鎖.png

7.2. 死鎖產(chǎn)生的必要條件(一定要熟悉)

以下這四個(gè)條件是死鎖的必要條件拍嵌,只要系統(tǒng)發(fā)生死鎖遭赂,這些條件必然成立,而只要上述條件之一不滿(mǎn)足横辆,就不會(huì)發(fā)生死鎖撇他。

7.2.1. 互斥條件

進(jìn)程要求對(duì)所分配的資源(如打印機(jī))進(jìn)行排他性控制茄猫,即在一段時(shí)間內(nèi)某資源僅為一個(gè)進(jìn)程所占有。此時(shí)若有其他進(jìn)程請(qǐng)求該資源困肩,則請(qǐng)求進(jìn)程只能等待划纽。

7.2.2. 不可剝奪條件

進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走锌畸,即只能由獲得該資源的進(jìn)程自己來(lái)釋放(只能是主動(dòng)釋放)勇劣。

7.2.3. 請(qǐng)求與保持條件

進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求潭枣,而該資源已被其他進(jìn)程占有比默,此時(shí)請(qǐng)求進(jìn)程被阻塞,但對(duì)自己已獲得的資源保持不放盆犁。

7.2.4. 循環(huán)等待條件

存在一種進(jìn)程資源的循環(huán)等待鏈命咐,鏈中每一個(gè)進(jìn)程已獲得的資源同時(shí)被 鏈中下一個(gè)進(jìn)程所請(qǐng)求。即存在一個(gè)處于等待狀態(tài)的進(jìn)程集合{Pl, P2, …, pn}谐岁,其中Pi等 待的資源被P(i+1)占有(i=0, 1, …, n-1)醋奠,Pn等待的資源被P0占有,如圖所示伊佃。

循環(huán)等待.png

滿(mǎn)足條件但無(wú)死循環(huán).png

7.2.5. 死鎖示例代碼

package cn.edu.nwafu.safe;

/**
 * @author shensr
 * @version V1.0
 * @description: 模擬死鎖
 * @create 2019/9/15
 **/

public class DeadLockRunnable implements Runnable {
    private static Object obj1 = new Object();//定義成靜態(tài)變量窜司,使線程可以共享實(shí)例
    private static Object obj2 = new Object();//定義成靜態(tài)變量,使線程可以共享實(shí)例
    public int flag ;

    public DeadLockRunnable(int flag) {
        this.flag = flag;
    }

    public void run() {
        if(flag == 1){
            System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj1航揉,正在請(qǐng)求obj2");
            synchronized (obj1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2){
                    System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj1和obj2");
                }
            }
        }
        if(flag==2){
            System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj2塞祈,正在請(qǐng)求obj1");
            synchronized (obj2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1){
                    System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj1和obj2");
                }
            }
        }
    }
}

測(cè)試:

package cn.edu.nwafu.safe;


/**
 * @author shensr
 * @version V1.0
 * @description: 測(cè)試死鎖
 * @create 2019/9/15
 **/

public class DeadLockRunnableTest {

    public static void main(String[] args) {
        //1.創(chuàng)建兩個(gè)DeadLockRunnable實(shí)例
        DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable(1);
        DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable(2);
        //2.創(chuàng)建兩個(gè)線程執(zhí)行兩個(gè)DeadLockRunnable實(shí)例
        Thread thread1 = new Thread(deadLockRunnable1, "runnable1");
        Thread thread2 = new Thread(deadLockRunnable2, "runnable2");
        thread1.start();
        thread2.start();
    }

}

執(zhí)行效果如下:表示死鎖產(chǎn)生

runnable1已經(jīng)獲取到obj1,正在請(qǐng)求obj2
runnable2已經(jīng)獲取到obj2迷捧,正在請(qǐng)求obj1

7.3. 死鎖處理

  • 預(yù)防死鎖:通過(guò)設(shè)置某些限制條件织咧,去破壞產(chǎn)生死鎖的四個(gè)必要條件中的一個(gè)或幾個(gè)條件,來(lái)防止死鎖的發(fā)生漠秋。
  • 避免死鎖:在資源的動(dòng)態(tài)分配過(guò)程中笙蒙,用某種方法去防止系統(tǒng)進(jìn)入不安全狀態(tài),從而避免死鎖的發(fā)生庆锦。
  • 檢測(cè)死鎖:允許系統(tǒng)在運(yùn)行過(guò)程中發(fā)生死鎖捅位,但可設(shè)置檢測(cè)機(jī)構(gòu)及時(shí)檢測(cè)死鎖的發(fā)生,并采取適當(dāng)措施加以清除搂抒。
  • 解除死鎖:當(dāng)檢測(cè)出死鎖后艇搀,便采取適當(dāng)措施將進(jìn)程從死鎖狀態(tài)中解脫出來(lái)。

7.3.1. 死鎖預(yù)防

預(yù)防死鎖是設(shè)法至少破壞產(chǎn)生死鎖的四個(gè)必要條件之一,嚴(yán)格的防止死鎖的出現(xiàn)求晶。

7.3.1.1. 破壞“互斥”條件

“互斥”條件是無(wú)法破壞的焰雕。因此,在死鎖預(yù)防里主要是破壞其他幾個(gè)必要條件芳杏,而不去涉及破壞“互斥”條件矩屁。

7.3.1.2. 破壞“占有并等待”條件

破壞“占有并等待”條件辟宗,就是在系統(tǒng)中不允許進(jìn)程在已獲得某種資源的情況下,申請(qǐng)其他資源吝秕。即要想出一個(gè)辦法泊脐,阻止進(jìn)程在持有資源的同時(shí)申請(qǐng)其他資源。

  • 方法一:一次性分配資源烁峭,即創(chuàng)建進(jìn)程時(shí)容客,要求它申請(qǐng)所需的全部資源,系統(tǒng)或滿(mǎn)足其所有要求约郁,或什么也不給它缩挑。
  • 方法二:要求每個(gè)進(jìn)程提出新的資源申請(qǐng)前,釋放它所占有的資源棍现。這樣调煎,一個(gè)進(jìn)程在需要資源S時(shí)镜遣,須先把它先前占有的資源R釋放掉己肮,然后才能提出對(duì)S的申請(qǐng),即使它可能很快又要用到資源R悲关。

7.3.1.3. 破壞“不可搶占”條件

破壞“不可搶占”條件就是允許對(duì)資源實(shí)行搶奪谎僻。

  • 方法一:如果占有某些資源的一個(gè)進(jìn)程進(jìn)行進(jìn)一步資源請(qǐng)求被拒絕,則該進(jìn)程必須釋放它最初占有的資源寓辱,如果有必要艘绍,可再次請(qǐng)求這些資源和另外的資源。
  • 方法二:如果一個(gè)進(jìn)程請(qǐng)求當(dāng)前被另一個(gè)進(jìn)程占有的一個(gè)資源秫筏,則操作系統(tǒng)可以搶占另一個(gè)進(jìn)程诱鞠,要求它釋放資源。只有在任意兩個(gè)進(jìn)程的優(yōu)先級(jí)都不相同的條件下这敬,方法二才能預(yù)防死鎖航夺。

7.3.1.4. 破壞“循環(huán)等待”條件

破壞“循環(huán)等待”條件的一種方法,是將系統(tǒng)中的所有資源統(tǒng)一編號(hào)崔涂,進(jìn)程可在任何時(shí)刻提出資源申請(qǐng)阳掐,但所有申請(qǐng)必須按照資源的編號(hào)順序(升序)提出。這樣做就能保證系統(tǒng)不出現(xiàn)死鎖冷蚂。

7.3.2. 死鎖避免

避免死鎖不嚴(yán)格限制產(chǎn)生死鎖的必要條件的存在,因?yàn)榧词顾梨i的必要條件存在,也不一定發(fā)生死鎖缭保。

7.3.2.1. 有序資源分配法

該算法實(shí)現(xiàn)步驟如下:

  • 必須為所有資源統(tǒng)一編號(hào),例如打印機(jī)為1蝙茶、傳真機(jī)為2艺骂、磁盤(pán)為3等
  • 同類(lèi)資源必須一次申請(qǐng)完,例如打印機(jī)和傳真機(jī)一般為同一個(gè)機(jī)器隆夯,必須同時(shí)申請(qǐng)
  • 不同類(lèi)資源必須按順序申請(qǐng)

例如:有兩個(gè)進(jìn)程P1和P2钳恕,有兩個(gè)資源R1和R2

P1請(qǐng)求資源:R1孕锄、R2

P2請(qǐng)求資源:R1、R2

這樣就破壞了環(huán)路條件苞尝,避免了死鎖的發(fā)生畸肆。

7.3.2.2. 銀行家算法

銀行家算法(Banker's Algorithm)是一個(gè)避免死鎖(Deadlock)的著名算法,是由艾茲格·迪杰斯特拉在1965年為T(mén).H.E系統(tǒng)設(shè)計(jì)的一種避免死鎖產(chǎn)生的算法宙址。它以銀行借貸系統(tǒng)的分配策略為基礎(chǔ)轴脐,判斷并保證系統(tǒng)的安全運(yùn)行。流程圖如下:

?
銀行家算法.png

銀行家算法的基本思想是分配資源之前抡砂,判斷系統(tǒng)是否是安全的大咱;若是,才分配注益。它是最具有代表性的避免死鎖的算法碴巾。

設(shè)進(jìn)程i提出請(qǐng)求REQUEST [i],則銀行家算法按如下規(guī)則進(jìn)行判斷丑搔。

  1. 如果REQUEST [i]<= NEED[i厦瓢,j],則轉(zhuǎn)(2)啤月;否則煮仇,出錯(cuò)。

  2. 如果REQUEST [i]<= AVAILABLE[i]谎仲,則轉(zhuǎn)(3)浙垫;否則,等待郑诺。

  3. 系統(tǒng)試探分配資源夹姥,修改相關(guān)數(shù)據(jù):

AVAILABLE[i]-=REQUEST[i];//可用資源數(shù)-請(qǐng)求資源數(shù)

ALLOCATION[i]+=REQUEST[i];//已分配資源數(shù)+請(qǐng)求資源數(shù)

NEED[i]-=REQUEST[i];//需要資源數(shù)-請(qǐng)求資源數(shù)

  1. 系統(tǒng)執(zhí)行安全性檢查,如安全辙诞,則分配成立辙售;否則試探險(xiǎn)性分配作廢,系統(tǒng)恢復(fù)原狀倘要,進(jìn)程等待圾亏。

7.3.2.3. 順序加鎖

當(dāng)多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖封拧,死鎖就很容易發(fā)生志鹃。

例如以下兩個(gè)線程就會(huì)死鎖:

Thread 1:

lock A (when C locked)

lock B (when C locked)

wait for C

Thread 2:

wait for A

wait for B

lock C (when A locked)

如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生泽西。 例如以下兩個(gè)線程就不會(huì)死鎖

Thread 1:

lock A

lock B

lock C

Thread 2:

wait for A

wait for B

wait for C

按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制曹铃。但是,這種方式需要事先知道所有可能會(huì)用到的鎖捧杉,但總有些時(shí)候是無(wú)法預(yù)知的陕见,所以該種方式只適合特定場(chǎng)景秘血。

7.3.2.4. 限時(shí)加鎖

限時(shí)加鎖是線程在嘗試獲取鎖的時(shí)候加一個(gè)超時(shí)時(shí)間,若超過(guò)這個(gè)時(shí)間則放棄對(duì)該鎖請(qǐng)求评甜,并回退并釋放所有已經(jīng)獲得的鎖灰粮,然后等待一段隨機(jī)的時(shí)間再重試

以下是一個(gè)例子,展示了兩個(gè)線程以不同的順序嘗試獲取相同的兩個(gè)鎖忍坷,在發(fā)生超時(shí)后回退并重試的場(chǎng)景:

Thread 1 locks A

Thread 2 locks B

Thread 1 attempts to lock B but is blocked

Thread 2 attempts to lock A but is blocked

Thread 1’s lock attempt on B times out

Thread 1 backs up and releases A as well

Thread 1 waits randomly (e.g. 257 millis) before retrying.

Thread 2’s lock attempt on A times out

Thread 2 backs up and releases B as well

Thread 2 waits randomly (e.g. 43 millis) before retrying.

在上面的例子中粘舟,線程2比線程1早200毫秒進(jìn)行重試加鎖,因此它可以先成功地獲取到兩個(gè)鎖佩研。這時(shí)柑肴,線程1嘗試獲取鎖A并且處于等待狀態(tài)。當(dāng)線程2結(jié)束時(shí)旬薯,線程1也可以順利的獲得這兩個(gè)鎖晰骑。

這種方式有兩個(gè)缺點(diǎn):

  1.     當(dāng)線程數(shù)量少時(shí),該種方式可避免死鎖绊序,但當(dāng)線程數(shù)量過(guò)多硕舆,這些線程的加鎖時(shí)限相同的概率就高很多,可能會(huì)導(dǎo)致超時(shí)后重試的死循環(huán)政模。
    
  2.     Java中不能對(duì)synchronized同步塊設(shè)置超時(shí)時(shí)間岗宣。你需要?jiǎng)?chuàng)建一個(gè)自定義鎖,或使用Java5中java.util.concurrent包下的工具淋样。
    

7.3.3. 死鎖檢測(cè)

預(yù)防和避免死鎖系統(tǒng)開(kāi)銷(xiāo)大且不能充分利用資源,更好的方法是不采取任何限制性措施胁住,而是提供檢測(cè)和解脫死鎖的手段趁猴,這就是死鎖檢測(cè)和恢復(fù)。

死鎖檢測(cè)數(shù)據(jù)結(jié)構(gòu):

  • E是現(xiàn)有資源向量(existing resource vector)彪见,代碼每種已存在資源的總數(shù)
  • A是可用資源向量(available resource vector)儡司,那么Ai表示當(dāng)前可供使用的資源數(shù)(即沒(méi)有被分配的資源)
  • C是當(dāng)前分配矩陣(current allocation matrix),C的第i行代表Pi當(dāng)前所持有的每一種類(lèi)型資源的資源數(shù)
  • R是請(qǐng)求矩陣(request matrix)余指,R的每一行代表P所需要的資源的數(shù)量

?
死鎖檢測(cè).png

死鎖檢測(cè)步驟:

  1.     尋找一個(gè)沒(méi)有結(jié)束標(biāo)記的進(jìn)程Pi捕犬,對(duì)于它而言R矩陣的第i行向量小于或等于A。
    
  2.     如果找到了這樣一個(gè)進(jìn)程酵镜,執(zhí)行該進(jìn)程碉碉,然后將C矩陣的第i行向量加到A中,標(biāo)記該進(jìn)程淮韭,并轉(zhuǎn)到第1步
    
  3.     如果沒(méi)有這樣的進(jìn)程垢粮,那么算法終止
    
  4.     算法結(jié)束時(shí),所有沒(méi)有標(biāo)記過(guò)的進(jìn)程都是死鎖進(jìn)程靠粪。
    

7.3.4. 死鎖恢復(fù)

利用搶占恢復(fù)蜡吧。

  • 臨時(shí)將某個(gè)資源從它的當(dāng)前所屬進(jìn)程轉(zhuǎn)移到另一個(gè)進(jìn)程毫蚓。

  • 這種做法很可能需要人工干預(yù),主要做法是否可行需取決于資源本身的特性昔善。

利用回滾恢復(fù)

  • 周期性的將進(jìn)程的狀態(tài)進(jìn)行備份元潘,當(dāng)發(fā)現(xiàn)進(jìn)程死鎖后,根據(jù)備份將該進(jìn)程復(fù)位到一個(gè)更早的君仆,還沒(méi)有取得所需的資源的狀態(tài)柬批,接著就把這些資源分配給其他死鎖進(jìn)程。

通過(guò)殺死進(jìn)程恢復(fù)

  • 最直接簡(jiǎn)單的方式就是殺死一個(gè)或若干個(gè)進(jìn)程袖订。

  • 盡可能保證殺死的進(jìn)程可以從頭再來(lái)而不帶來(lái)副作用氮帐。

8. 線程通訊

8.1. 為什么要線程通信

多個(gè)線程并發(fā)執(zhí)行時(shí),在默認(rèn)情況下CPU是隨機(jī)切換線程的洛姑,有時(shí)我們希望CPU按我們的規(guī)律執(zhí)行線程上沐,此時(shí)就需要線程之間協(xié)調(diào)通信。

8.2. 線程通訊方式

線程間通信常用方式如下:

  • 休眠喚醒方式:

Object的wait楞艾、notify参咙、notifyAll

Condition的await、signal硫眯、signalAll

  • CountDownLatch:用于某個(gè)線程A等待若干個(gè)其他線程執(zhí)行完之后蕴侧,它才執(zhí)行
  • CyclicBarrier:一組線程等待至某個(gè)狀態(tài)之后再全部同時(shí)執(zhí)行
  • Semaphore:用于控制對(duì)某組資源的訪問(wèn)權(quán)限

8.2.1. 休眠喚醒方式

多線程打印10以?xún)?nèi)的奇偶數(shù):

i從0開(kāi)始,當(dāng)i是奇數(shù)時(shí)两入,奇數(shù)線程打印净宵,偶數(shù)線程等待;

? 當(dāng)i是偶數(shù)時(shí)裹纳,偶數(shù)線程打印择葡,奇數(shù)線程等待。

方式一:Object的wait剃氧、notify敏储、notifyAll

package cn.edu.nwafu.communication;

/**
 * @author shensr
 * @version V1.0
 * @description: **多線程打印10以?xún)?nèi)的奇偶數(shù):**
 *                  i從0開(kāi)始,當(dāng)i是奇數(shù)時(shí)朋鞍,奇數(shù)線程打印已添,偶數(shù)線程等待;
 * ?                         當(dāng)i是偶數(shù)時(shí)滥酥,偶數(shù)線程打印更舞,奇數(shù)線程等待。 
 *              使用Object的wait恨狈、notify疏哗、notifyAll方式實(shí)現(xiàn)線程通訊
 * @create 2019/9/16
 **/

public class WaitNotifyRunnable {
    private Object obj = new Object();
    private Integer i=0;
    
    public void odd() {
        while(i<10){
            synchronized (obj){
                if(i%2 == 1){
                    System.out.println("奇數(shù):"+i);
                    i++;
                    obj.notify();
                } else {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public void even(){
        while(i<10){
            synchronized (obj){
                if(i%2 == 0){
                    System.out.println("偶數(shù):"+i);
                    i++;
                    obj.notify();
                } else {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    public static void main(String[] args){
        final WaitNotifyRunnable runnable = new WaitNotifyRunnable();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                runnable.odd();
            }
        }, "偶數(shù)線程");
        //lambda表達(dá)式
        Thread t2 = new Thread(() -> runnable.even(), "奇數(shù)線程");

        t1.start();
        t2.start();
    }
}

注意: Object的wait、notify、notifyAll這些方法依賴(lài)于synchronized關(guān)鍵字返奉,沒(méi)有就會(huì)拋出java.lang.IllegalMonitorStateException異常

方式二:Condition的await贝搁、signal、signalAll

package cn.edu.nwafu.communication.conditon;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author shensr
 * @version V1.0
 * @description:
 * @create 2019/9/16
 **/

public class WaitNotifyRunnable {

    private Lock lock = new ReentrantLock();//這里要設(shè)置為獨(dú)占鎖芽偏,參數(shù)要為false
    private Condition condition = lock.newCondition();
    private Integer i=0;
    public void odd() {
        while(i<10){
            lock.lock();
            try{
                if(i%2 == 1){
                    System.out.println("奇數(shù):"+i);
                    i++;
                    condition.signal();
                } else {
                    condition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }

    public void even(){
        while(i<10){
            lock.lock();
            try{
                if(i%2 == 0){
                    System.out.println("偶數(shù):"+i);
                    i++;
                    condition.signal();
                } else {
                    condition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
    public static void main(String[] args){
        final WaitNotifyRunnable runnable = new WaitNotifyRunnable();
        Thread t1 = new Thread(()->runnable.odd(), "偶數(shù)線程");
        Thread t2 = new Thread(() -> runnable.even(), "奇數(shù)線程");
        t1.start();
        t2.start();
    }
}

Object和Condition休眠喚醒區(qū)別

  • object wait()必須在synchronized(同步鎖)下使用雷逆,
  • object wait()必須要通過(guò)notify()方法進(jìn)行喚醒
  • condition await() 必須和Lock(互斥鎖/共享鎖)配合使用
  • condition await() 必須通過(guò) signal() 方法進(jìn)行喚醒

8.2.2. CountDownLatch方式

CountDownLatch是在java1.5被引入的,存在于java.util.concurrent包下污尉。

CountDownLatch這個(gè)類(lèi)能夠使一個(gè)線程等待其他線程完成各自的工作后再執(zhí)行膀哲。

CountDownLatch是通過(guò)一個(gè)計(jì)數(shù)器來(lái)實(shí)現(xiàn)的,計(jì)數(shù)器的初始值為線程的數(shù)量被碗。

CountDownLatch方式.png

每當(dāng)一個(gè)線程完成了自己的任務(wù)后某宪,計(jì)數(shù)器的值就會(huì)減1。當(dāng)計(jì)數(shù)器值到達(dá)0時(shí)锐朴,它表示所有的線程已經(jīng)完成了任務(wù)兴喂,然后在閉鎖上等待的線程就可以恢復(fù)執(zhí)行任務(wù)脯颜。

示例代碼:

package cn.edu.nwafu.communication.countDownLatch;

import java.util.concurrent.CountDownLatch;

/**
 * @author shensr
 * @version V1.0
 * @description: CountDownLatch方式
 * @create 2019/9/16
 **/

public class CountDown {
    private Integer i = 0;
    private CountDownLatch countDownLatch = new CountDownLatch(1);

    public void odd(){
        while(i < 10){
            if(i%2 == 1){
                System.out.println("奇數(shù):"+i);
                i++;
                countDownLatch.countDown();
            } else {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void even(){
        while(i < 10){
            if(i%2 == 0){
                System.out.println("偶數(shù):"+i);
                i++;
                countDownLatch.countDown();
            } else {
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args){
        final CountDown countDown = new CountDown();
        Thread t1 = new Thread(() -> countDown.odd(),"奇數(shù)");
        Thread t2 = new Thread(() -> countDown.even(),"偶數(shù)");

        t1.start();
        t2.start();
    }
}

8.2.3. CyclicBarrier方式

CyclicBarrier是在java1.5被引入的放接,存在于java.util.concurrent包下慷垮。

CyclicBarrier實(shí)現(xiàn)讓一組線程等待至某個(gè)狀態(tài)之后再全部同時(shí)執(zhí)行冒冬。

CyclicBarrier底層是基于ReentrantLock和Condition實(shí)現(xiàn)。

三個(gè)線程同時(shí)啟動(dòng)实柠,示例代碼如下:

package cn.edu.nwafu.communication.cyclicbarrier;

import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @author shensr
 * @version V1.0
 * @description:
 * @create 2019/9/16
 **/

public class CyclicBarrierDemo {
     private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

    public static void run(){
        System.out.println(Thread.currentThread().getName()+":準(zhǔn)備...");
        try {
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"啟動(dòng)完畢:"+new Date().getTime());

    }

    public static void main(String[] args){
        new Thread(() -> run(),"線程1").start();
        new Thread(() -> run(),"線程2").start();
        new Thread(() -> run(),"線程3").start();
    }
}

執(zhí)行效果如下:三個(gè)線程同時(shí)啟動(dòng)

線程1:準(zhǔn)備...
線程2:準(zhǔn)備...
線程3:準(zhǔn)備...
線程3啟動(dòng)完畢:1568605543873
線程1啟動(dòng)完畢:1568605543873
線程2啟動(dòng)完畢:1568605543873

8.2.4. Semaphore方式

Semaphore是在java1.5被引入的橱脸,存在于java.util.concurrent包下抒和。

Semaphore用于控制對(duì)某組資源的訪問(wèn)權(quán)限膳沽。

工人使用機(jī)器工作汗菜,示例代碼如下:

package com.multithread.thread;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

    static class Machine implements Runnable{
        private int num;
        private Semaphore semaphore;

        public Machine(int num, Semaphore semaphore) {
            this.num = num;
            this.semaphore = semaphore;
        }

        public void run() {
            try {
                semaphore.acquire();//請(qǐng)求機(jī)器
                System.out.println("工人"+this.num+"請(qǐng)求機(jī)器,正在使用機(jī)器");
                Thread.sleep(1000);
                System.out.println("工人"+this.num+"使用完畢贵少,已經(jīng)釋放機(jī)器");
                semaphore.release();//釋放機(jī)器
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        int worker = 8;//工人數(shù)
        Semaphore semaphore = new Semaphore(3);//機(jī)器數(shù)
        for (int i=0; i< worker; i++){
            new Thread(new Machine(i, semaphore)).start();
        }
    }
}

執(zhí)行效果如下:

工人[1]請(qǐng)求機(jī)器呵俏,正在使用機(jī)器...
工人[0]請(qǐng)求機(jī)器,正在使用機(jī)器...
工人[2]請(qǐng)求機(jī)器滔灶,正在使用機(jī)器...
工人[0]使用完畢,已經(jīng)釋放機(jī)器!!!
工人[1]使用完畢吼肥,已經(jīng)釋放機(jī)器!!!
工人[3]請(qǐng)求機(jī)器录平,正在使用機(jī)器...
工人[4]請(qǐng)求機(jī)器,正在使用機(jī)器...
工人[2]使用完畢缀皱,已經(jīng)釋放機(jī)器!!!
工人[6]請(qǐng)求機(jī)器斗这,正在使用機(jī)器...
工人[3]使用完畢,已經(jīng)釋放機(jī)器!!!
工人[4]使用完畢啤斗,已經(jīng)釋放機(jī)器!!!
工人[5]請(qǐng)求機(jī)器表箭,正在使用機(jī)器...
工人[7]請(qǐng)求機(jī)器,正在使用機(jī)器...
工人[6]使用完畢钮莲,已經(jīng)釋放機(jī)器!!!
工人[7]使用完畢免钻,已經(jīng)釋放機(jī)器!!!
工人[5]使用完畢彼水,已經(jīng)釋放機(jī)器!!!

8.3 小結(jié)

8.3.1. sleep和wait區(qū)別

wait sleep
同步 只能在同步上下文中調(diào)用wait方法,否則拋出java.lang.IllegalMonitorStateException異常 不需要在同步方法或同步代碼塊中調(diào)用
作用對(duì)象 wait方法定義在Object類(lèi)中极舔,作用于對(duì)象本身 sleep方法定義在java.lang.Thread中凤覆,作用于當(dāng)前線程
釋放鎖資源
喚醒條件 其他線程調(diào)用對(duì)象的notify()方法或則notifyAll()方法 超時(shí)或則調(diào)用interrupt方法
方法屬性 wait是實(shí)例方法 sleep是靜態(tài)方法

8.3.2. wait和notify區(qū)別

  • wait和notify都是Object中的方法

  • wait和notify執(zhí)行前線程都必須獲得對(duì)象鎖

  • wait的作用是使當(dāng)前線程進(jìn)行等待

  • notify的作用是通知其他等待當(dāng)前線程的對(duì)象鎖的線程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拆魏,隨后出現(xiàn)的幾起案子盯桦,更是在濱河造成了極大的恐慌,老刑警劉巖渤刃,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拥峦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡卖子,警方通過(guò)查閱死者的電腦和手機(jī)略号,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)揪胃,“玉大人璃哟,你說(shuō)我怎么就攤上這事『暗荩” “怎么了随闪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)骚勘。 經(jīng)常有香客問(wèn)我铐伴,道長(zhǎng),這世上最難降的妖魔是什么俏讹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任当宴,我火速辦了婚禮,結(jié)果婚禮上泽疆,老公的妹妹穿的比我還像新娘户矢。我一直安慰自己,他們只是感情好殉疼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布梯浪。 她就那樣靜靜地躺著,像睡著了一般瓢娜。 火紅的嫁衣襯著肌膚如雪挂洛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天眠砾,我揣著相機(jī)與錄音虏劲,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柒巫,可吹牛的內(nèi)容都是我干的励堡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吻育,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼念秧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起布疼,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摊趾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后游两,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體砾层,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年贱案,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肛炮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宝踪,死狀恐怖侨糟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瘩燥,我是刑警寧澤秕重,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站厉膀,受9級(jí)特大地震影響溶耘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜服鹅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一凳兵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧企软,春花似錦庐扫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至藻治,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巷挥,已是汗流浹背桩卵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雏节。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓胜嗓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钩乍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辞州,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來(lái)是分開(kāi)三篇的,后來(lái)想想還是整...
    coder_pig閱讀 1,653評(píng)論 2 17
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類(lèi) 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,957評(píng)論 1 18
  • 本文主要講了java中多線程的使用方法寥粹、線程同步变过、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法涝涤、概述等媚狰。 首先講...
    李欣陽(yáng)閱讀 2,456評(píng)論 1 15
  • 一、基本概念:程序 - 進(jìn)程 - 線程 程序(program):是為完成特定任務(wù)阔拳、用某種語(yǔ)言編寫(xiě)的一組指令的集合崭孤。...
    c5fc16271aee閱讀 468評(píng)論 0 2
  • 一個(gè)人從最開(kāi)始呱呱落地到走完人生路長(zhǎng)眠于地下最離不開(kāi)的就是飲食。飲食與人生密不可分糊肠”娉瑁《漢書(shū)·酈食其傳》:“王者以...
    周智東智周閱讀 813評(píng)論 0 3