多線程學(xué)習(xí)(基礎(chǔ))

一灾挨、什么是多線程

程序:

是為完成特定任務(wù),用某種語(yǔ)言編寫的一組指令的集合眶诈,即指一段靜態(tài)的代碼涨醋,靜態(tài)對(duì)象

進(jìn)程:

是程序的一次執(zhí)行過(guò)程,或是正在運(yùn)行的一個(gè)程序逝撬。是一個(gè)動(dòng)態(tài)的過(guò)程:有他自身的產(chǎn)生浴骂,存在和消亡的過(guò)程。運(yùn)行中的QQ宪潮,運(yùn)行中的播放器都是一個(gè)進(jìn)程溯警;程序是靜態(tài)的,進(jìn)程是動(dòng)態(tài)的狡相;進(jìn)程作為資源分配的單位梯轻,系統(tǒng)在運(yùn)行時(shí)會(huì)為每個(gè)進(jìn)程分配不同的內(nèi)存區(qū)域。

線程:

進(jìn)程可進(jìn)一步細(xì)化為線程尽棕,是一個(gè)程序內(nèi)部的一條執(zhí)行路徑喳挑。若一個(gè)進(jìn)程同一時(shí)間并行執(zhí)多個(gè)線程,就是支持多線程的;線程作為調(diào)度和執(zhí)行的單位伊诵,每個(gè)線程擁有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器单绑,線程切換的開銷小曹宴;一個(gè)進(jìn)程中搂橙,多個(gè)線程共享相同的內(nèi)存單元/內(nèi)存地址空間,也就是說(shuō)他們從同一堆中訪問(wèn)相同的變量和對(duì)象笛坦,這就使得線程間通信更簡(jiǎn)便区转、高效。但多個(gè)線程操作共享的系統(tǒng)資源可能就會(huì)帶來(lái)安全的隱患版扩。

jvm虛擬機(jī)結(jié)構(gòu)

單核CPU和多核CPU的理解

單核CPU:其實(shí)就是一種假的多線程废离,因?yàn)樵谝粋€(gè)時(shí)間單元內(nèi),也只能執(zhí)行一個(gè)線程的任務(wù)资厉。例如:雖然有多車道厅缺,但是收費(fèi)站只有一個(gè)工作人員在收費(fèi),只有收了費(fèi)才能通過(guò)宴偿,那么CPU就好比收費(fèi)人員湘捎,如果某個(gè)人不想交錢,那么收費(fèi)員就可以把他"掛起"窄刘。但是因?yàn)镃PU時(shí)間單元特別短窥妇,因此感覺不出來(lái)
如果是多核CPU才能更好的發(fā)揮多線程的效率。
一個(gè)java的程序java.exe娩践,其實(shí)至少有三個(gè)線程活翩,main()主線程,gc()垃圾回收線程翻伺,異常處理線程材泄。當(dāng)然如果發(fā)生異常,會(huì)影響主線程吨岭。

并行和并發(fā)

并行:多個(gè)CPU同時(shí)執(zhí)行多個(gè)任務(wù)拉宗。比如:多個(gè)人同時(shí)做不同的事。
并發(fā):一個(gè)CPU(采用時(shí)間片)同時(shí)執(zhí)行多個(gè)任務(wù)辣辫。比如:秒殺旦事,多個(gè)人做同一件事

何時(shí)需要多線程

1)程序需要同時(shí)執(zhí)行兩個(gè)或多個(gè)任務(wù)
2)程序需要實(shí)現(xiàn)一些需要等待的任務(wù)時(shí),如用戶輸入急灭、文件讀寫操作姐浮、網(wǎng)絡(luò)操作、搜索等葬馋。
3)需要一些后臺(tái)運(yùn)行的程序時(shí)卖鲤。

線程的調(diào)度

1)時(shí)間片:同優(yōu)先級(jí)線程組成先進(jìn)先出隊(duì)列(先到先服務(wù))肾扰,使用時(shí)間片策略
2)搶占式:對(duì)高優(yōu)先級(jí),使用優(yōu)先調(diào)度的搶占式策略

二扫尖、多線程的創(chuàng)建

1白对、繼承Thread類

1)創(chuàng)建一個(gè)繼承于Thread的類
2)重寫Thread的run()方法
3)創(chuàng)建Thread類的子類對(duì)象
4)通過(guò)此對(duì)象調(diào)用Thread的start()方法
(start方法的作用:①啟動(dòng)當(dāng)前線程,②調(diào)用當(dāng)前線程的run)

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

1)創(chuàng)建一個(gè)實(shí)現(xiàn)了Runnable的接口
2)實(shí)現(xiàn)類去實(shí)現(xiàn)run()方法
3)創(chuàng)建實(shí)現(xiàn)類對(duì)象
4)將此對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對(duì)象
5)通過(guò)Thread類的對(duì)象調(diào)用start()

3蟀瞧、實(shí)現(xiàn)Callable接口

1)創(chuàng)建一個(gè)實(shí)現(xiàn)Callable的實(shí)現(xiàn)類
2)實(shí)現(xiàn)call()方法沉颂,將此線程需要執(zhí)行的操作聲明在call()中
3)創(chuàng)建Callable接口實(shí)現(xiàn)類的對(duì)象
4)將此Callable接口實(shí)現(xiàn)類的對(duì)象傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask對(duì)象
5)將FutureTask的對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中悦污,創(chuàng)建Thread對(duì)象铸屉,并調(diào)用start()
6)獲取Callable中call方法的返回值

4、使用線程池創(chuàng)建

背景:經(jīng)常創(chuàng)建和銷毀切端,使用量特別大的資源,比如并發(fā)情況下的線程對(duì)性能影響很大。
思路:提前創(chuàng)建好多個(gè)線程藕夫,放入線程池中丸凭,使用時(shí)直接獲取,使用后放回池中茵瀑,可以避免頻繁創(chuàng)建銷毀间驮,實(shí)現(xiàn)重復(fù)利用,類似生活中的公共交通工具马昨。
好處:提高響應(yīng)速度(減少了創(chuàng)建新線程的時(shí)間)竞帽;降低資源消耗(重復(fù)利用線程池中的線程,不需要每次都創(chuàng)建)鸿捧;便于線程管理
corePoolSize:核心池的大小
maximumPoolSize:最大線程數(shù)
keepAliveTime:線程沒有任務(wù)時(shí)最多保持多長(zhǎng)時(shí)間后會(huì)終止

5屹篓、Thread和Runnable參考示例如下

1)Thread
package com.xigu.concurrent;


/**
 * 1)創(chuàng)建一個(gè)繼承于Thread的類
 * 2)重寫Thread的run()方法
 * 3)創(chuàng)建Thread類的子類對(duì)象
 * 4)通過(guò)此對(duì)象調(diào)用Thread的start()方法
 * (start方法的作用:①啟動(dòng)當(dāng)前線程,②調(diào)用當(dāng)前線程的run)
 */
class ThreadDemo extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println("*************thread1**************" + i);
            }
        }
    }
}
public class ThreadTest extends Thread {
    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        t1.start();
//       問(wèn)題一:不能直接調(diào)用線程的run()方法,需要重新創(chuàng)建一個(gè)線程的對(duì)象
//       t1.run();
//       ThreadDemo t2 = new ThreadDemo();
//       t2.start();

        //如下操作仍然是在main線程中執(zhí)行
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println("***************main**************" + i);
            }
        }
    }
}
2)Runnable
package com.xigu.concurrent;

/**
 * 1)創(chuàng)建一個(gè)實(shí)現(xiàn)了Runnable的接口
 * 2)實(shí)現(xiàn)類去實(shí)現(xiàn)run()方法
 * 3)創(chuàng)建實(shí)現(xiàn)類對(duì)象
 * 4)將此對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中匙奴,創(chuàng)建Thread類的對(duì)象
 * 5)通過(guò)Thread類的對(duì)象調(diào)用start()
 */

//創(chuàng)建一個(gè)實(shí)現(xiàn)了Runnable的接口
class MyRunnable implements Runnable {
    //實(shí)現(xiàn)類去實(shí)現(xiàn)run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println("*************thread1**************" + i);
            }
        }
    }
}

public class RunnableTest {
    public static void main(String[] args) {
        //創(chuàng)建實(shí)現(xiàn)類對(duì)象
        MyRunnable myRunnable = new MyRunnable();
        //將此對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中堆巧,創(chuàng)建Thread類的對(duì)象
        Thread t1 = new Thread(myRunnable);
        //通過(guò)Thread類的對(duì)象調(diào)用start()
        t1.start();
    }
}
3)Callable
package com.xigu.concurrent;

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

/**
 * 1)創(chuàng)建一個(gè)實(shí)現(xiàn)Callable的實(shí)現(xiàn)類
 * 2)實(shí)現(xiàn)call()方法,將此線程需要執(zhí)行的操作聲明在call()中
 * 3)創(chuàng)建Callable接口實(shí)現(xiàn)類的對(duì)象
 * 4)將此Callable接口實(shí)現(xiàn)類的對(duì)象傳遞到FutureTask構(gòu)造器中饥脑,創(chuàng)建FutureTask對(duì)象
 * 5)將FutureTask的對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中恳邀,創(chuàng)建Thread對(duì)象,并調(diào)用start()
 * 6)獲取Callable中call方法的返回值
 */

//1)創(chuàng)建一個(gè)實(shí)現(xiàn)Callable的實(shí)現(xiàn)類
class MyCallable implements Callable {
    //實(shí)現(xiàn)call()方法灶轰,將此線程需要執(zhí)行的操作聲明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println("*************thread1**************" + i);
                sum += i;
            }
        }
        return sum;
    }
}


public class CallableTest {
    public static void main(String[] args) {
        //3)創(chuàng)建Callable接口實(shí)現(xiàn)類的對(duì)象
        MyCallable myCallable = new MyCallable();
        //4)將此Callable接口實(shí)現(xiàn)類的對(duì)象傳遞到FutureTask構(gòu)造器中谣沸,創(chuàng)建FutureTask對(duì)象
        FutureTask futureTask = new FutureTask(myCallable);
        //5)將FutureTask的對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對(duì)象笋颤,并調(diào)用start()
        Thread t1 = new Thread(futureTask);
        t1.start();
        try {
            //獲取到Callable的返回值乳附,get()方法一般放在最后面
            Object sum = futureTask.get();
            System.out.println("總和為" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
4)線程池
package com.xigu.concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolTest {
    public static void main(String[] args) {
        //1)提供指定線程數(shù)量的線程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //設(shè)置線程池的屬性
        System.out.println(service1.getClass());
        service1.setCorePoolSize(10);
        //service1.setKeepAliveTime(10);

        //2)執(zhí)行指定的線程的操作内地。需要提供實(shí)現(xiàn)Runnable接口或Callable接口實(shí)現(xiàn)類的對(duì)象
        service1.execute(new ThreadDemo());//適用于Runnable
        service1.submit(new CallableDemo());//適用于Callable
        //3)關(guān)閉連接池
        service.shutdown();
    }
}

//創(chuàng)建一個(gè)實(shí)現(xiàn)了Runnable的接口
class RunableDemo implements Runnable {
    //實(shí)現(xiàn)類去實(shí)現(xiàn)run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println("*************thread**************" + i);
            }
        }
    }
}

//創(chuàng)建一個(gè)實(shí)現(xiàn)了Callable的接口
class CallableDemo implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println("*************callable**************" + i);
                sum += i;
            }
        }
        return sum;
    }
}

6、繼承Thread和實(shí)現(xiàn)Runnable方法對(duì)比

開發(fā)中:優(yōu)先選擇赋除,實(shí)現(xiàn)Runnable接口的方式
原因:
1阱缓、實(shí)現(xiàn)的方式?jīng)]有類的單繼承性的局限性
2、實(shí)現(xiàn)的方式更適合來(lái)處理多個(gè)線程有共享數(shù)據(jù)的情況
3举农、線程池只能放入實(shí)現(xiàn)Runable或Callable類線程荆针,不能直接放入繼承Thread的類
聯(lián)系:public class Thread implements Runnable(也就是說(shuō)實(shí)際上Thread也是Runnable的一個(gè)實(shí)現(xiàn)類)
相同點(diǎn):兩種方式都需要重寫run(),將線程要執(zhí)行的邏輯聲明在run()中

Runnable
Thread

上圖對(duì)比原因是:MyThread創(chuàng)建了兩個(gè)實(shí)例颁糟,自然會(huì)賣出兩倍航背,屬于用法錯(cuò)誤

7、實(shí)現(xiàn)Runnable和實(shí)現(xiàn)Callable作對(duì)比

相比于Runnable棱貌,Callable方法更強(qiáng)大玖媚,相比run()方法,可以有返回值婚脱,方法可以拋出異常今魔,支持泛型的返回值。需要借助于FutureTask類障贸,比如獲取返回結(jié)果時(shí)错森。

三、Thread的常用方法

  • 1惹想、start():啟動(dòng)當(dāng)前線程问词,調(diào)用當(dāng)前線程的run()
  • 2、run():通常需要重寫Thread類中的此方法嘀粱,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中
  • 3激挪、currentThread():靜態(tài)方法,返回執(zhí)行當(dāng)前代碼的線程
  • 4锋叨、getName():獲取當(dāng)前線程的名字
  • 5垄分、setName():設(shè)置當(dāng)前線程的名字
  • 6、yield():釋放當(dāng)前cpu的執(zhí)行權(quán)
  • 7娃磺、join():在線程a中調(diào)用線程b的join()薄湿,此時(shí)線程a進(jìn)入阻塞狀態(tài),直到線程b完全執(zhí)行完成后偷卧,線程a才結(jié)束阻塞狀態(tài)
  • 8豺瘤、stop():已過(guò)時(shí)。當(dāng)執(zhí)行此方法時(shí)听诸,強(qiáng)制結(jié)束當(dāng)前線程
  • 9坐求、sleep(long millitime):讓當(dāng)前線程"睡眠"指定的millitime毫秒,在指定的millitime毫秒時(shí)間內(nèi)晌梨,當(dāng)前線程是阻塞狀態(tài)
  • 10桥嗤、isAlive():判斷當(dāng)前線程是否存活

sleep()须妻、wait()、join()泛领、yield()的區(qū)別

一荒吏、鎖池和等待池維度
1.鎖池:
所有需要競(jìng)爭(zhēng)同步鎖的線程都會(huì)放在鎖池當(dāng)中,比如當(dāng)前對(duì)象的鎖已經(jīng)被其中一個(gè)線程得到渊鞋,則其他線程需要在這個(gè)鎖池進(jìn)行等待绰更,當(dāng)前面的線程釋放同步鎖后鎖池中的線程去競(jìng)爭(zhēng)同步鎖,當(dāng)某個(gè)線程得到后會(huì)進(jìn)入就緒隊(duì)列進(jìn)行等待cpu資源分配锡宋。
2.等待池
當(dāng)我們調(diào)用wait()方法后动知,線程會(huì)放到等待池當(dāng)中,等待池的線程是不會(huì)去競(jìng)爭(zhēng)同步鎖员辩。只有調(diào)用了notify()或notifyAll()后等待池的線程才會(huì)開始去競(jìng)爭(zhēng)鎖,notify()是隨機(jī)從等待池選出一個(gè)線程放到鎖池鸵鸥,而notifyAll()是將等待池的所有線程放到鎖池當(dāng)中
二奠滑、其他區(qū)別
1、sleep 是 Thread 類的靜態(tài)本地方法妒穴,wait 則是 Object 類的本地方法宋税。
2、sleep方法不會(huì)釋放lock讼油,但是wait會(huì)釋放杰赛,而且會(huì)加入到等待隊(duì)列中。
sleep就是把cpu的執(zhí)行資格和執(zhí)行權(quán)釋放出去矮台,不再運(yùn)行此線程乏屯,當(dāng)定時(shí)時(shí)間結(jié)束再取回cpu資源,參與cpu的調(diào)度瘦赫,獲取到cpu資源后就可以繼續(xù)運(yùn)行了辰晕。而如果sleep時(shí)該線程有鎖,那么sleep不會(huì)釋放這個(gè)鎖确虱,而是把鎖帶著進(jìn)入了凍結(jié)狀態(tài)含友,也就是說(shuō)其他需要這個(gè)鎖的線程根本不可能獲取到這個(gè)鎖。也就是說(shuō)無(wú)法執(zhí)行程序校辩。如果在睡眠期間其他線程調(diào)用了這個(gè)線程的interrupt方法窘问,那么這個(gè)線程也會(huì)拋出interruptexception異常返回,這點(diǎn)和wait是一樣的宜咒。
3惠赫、sleep方法不依賴于同步器synchronized,但是wait需要依賴synchronized關(guān)鍵字荧呐。
4汉形、sleep不需要被喚醒(休眠之后退出阻塞)纸镊,但是wait需要(不指定時(shí)間需要被別人中斷)。
5概疆、sleep 一般用于當(dāng)前線程休眠逗威,或者輪循暫停操作,wait 則多用于多線程之間的通信岔冀。
6凯旭、sleep 會(huì)讓出 CPU 執(zhí)行時(shí)間且強(qiáng)制上下文切換,而wait則不一定使套,wait 后可能還是有機(jī)會(huì)重新競(jìng)爭(zhēng)到鎖繼續(xù)執(zhí)行的罐呼。yield()執(zhí)行后線程直接進(jìn)入就緒狀態(tài),馬上釋放了cpu的執(zhí)行權(quán)侦高,但是依然保留了cpu的執(zhí)行資格嫉柴,所以有可能cpu下次進(jìn)行線程調(diào)度還會(huì)讓這個(gè)線程獲取到執(zhí)行權(quán)繼續(xù)執(zhí)行。join()執(zhí)行后線程進(jìn)入阻塞狀態(tài)奉呛,例如在線程B中調(diào)用線程A的join()计螺,那線程B會(huì)進(jìn)入到阻塞隊(duì)列,直到線程A結(jié)束或中斷線程瞧壮。

public static void main(String[] args) throws InterruptedException { 
  Thread t1 = new Thread(new Runnable() { 
  @Override
     public void run() { 
        try {
            Thread.sleep(3000); 
        } catch (InterruptedException e) {
            e.printStackTrace(); 
        }
        System.out.println("22222222");
       } 
  }); 
  t1.start();
  t1.join();   
  //這行代碼必須要等t1全部執(zhí)行完畢登馒,才會(huì)執(zhí)行         
  System.out.println("1111"); }

##輸出結(jié)果
##22222222
##1111

四、線程的分類

java中線程分為兩類:一種是守護(hù)線程咆槽,一種是用戶線程
兩者幾乎是相同的陈轿,唯一的區(qū)別就是判斷JVM何時(shí)離開。守護(hù)線程是用來(lái)服務(wù)用戶線程的秦忿,通過(guò)在start()方法前調(diào)用thread.setDaemon(true)可以把一個(gè)用戶線程變成一個(gè)守護(hù)線程麦射。
java的垃圾回收就是一個(gè)典型的守護(hù)線程,若JVM中都是守護(hù)線程小渊,當(dāng)前JVM將退出法褥。
形象理解:兔死狗烹、鳥盡弓藏

五酬屉、線程的生命周期

JDK中用Thread.state類定義了線程的幾種狀態(tài)半等,在他完整的生命周期中通常要經(jīng)歷以下五種狀態(tài):
1)新建:當(dāng)一個(gè)Thread類或其子類的對(duì)象被聲明并創(chuàng)建時(shí),新生的線程對(duì)象處于新建狀態(tài)呐萨。
2)就緒:處于新建狀態(tài)的線程被start()后杀饵,將進(jìn)入線程隊(duì)列等待CPU時(shí)間片,此時(shí)它已具備了運(yùn)行的條件谬擦,只是沒分配到CPU資源切距。
3)運(yùn)行:當(dāng)就緒的線程被調(diào)度并獲得到CPU資源時(shí),便進(jìn)入了運(yùn)行狀態(tài)惨远,run()方法定義了線程的操作和功能谜悟。
4)阻塞:在某種特殊情況下话肖,被人掛起或執(zhí)行輸入輸出操作時(shí),讓出CPU并臨時(shí)中止自己的執(zhí)行葡幸,進(jìn)入阻塞狀態(tài)最筒。
5)死亡:線程完成了他的全部工作或線程被提前強(qiáng)制性的中止或出現(xiàn)異常導(dǎo)致結(jié)束。

阻塞的情況又分為三種:
(1)蔚叨、等待阻塞:運(yùn)行的線程執(zhí)行wait方法床蜘,該線程會(huì)釋放占用的所有資源,JVM會(huì)把該線程放入“等待池”中蔑水。進(jìn)入這個(gè)狀態(tài)后邢锯,是不能自動(dòng)喚醒的,必須依靠其他線程調(diào)用notify或notifyAll方法才能被喚醒搀别,wait是object類的方法丹擎。
(2)、同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí)歇父,若該同步鎖被別的線程占用鸥鹉,則JVM會(huì)把該線程放入“鎖池”中。
(3)庶骄、其他阻塞:運(yùn)行的線程執(zhí)行sleep或join方法,或者發(fā)出了I/O請(qǐng)求時(shí)践磅,JVM會(huì)把該線程置為阻塞狀態(tài)单刁。當(dāng)sleep狀態(tài)超時(shí)、join等待線程終止或者超時(shí)府适、或者I/O處理完畢時(shí)羔飞,線程重新轉(zhuǎn)入就緒狀態(tài)。sleep是Thread類的方法檐春。

線程的生命周期的切換

六逻淌、線程的同步問(wèn)題

如下所示:假如原來(lái)有余額3000,有兩個(gè)線程疟暖,都要對(duì)賬戶余額進(jìn)行扣款2000卡儒,舉例說(shuō)首先要驗(yàn)證一下2000<3000,驗(yàn)證通過(guò)了俐巴,但是還沒有進(jìn)行下一步骨望,這時(shí)候又來(lái)了一個(gè)線程,也是先驗(yàn)證一下欣舵,也通過(guò)了擎鸠。然后兩個(gè)線程都進(jìn)行下一步扣款,這樣就導(dǎo)致了缘圈,出現(xiàn)了-1000劣光。

問(wèn)題示例

6-1解決方法:

1袜蚕、同步代碼塊

synchronized(同步監(jiān)視器){
//需要被同步的代碼
}

說(shuō)明:
1、操作共享數(shù)據(jù)的代碼绢涡,即為需要被同步的代碼
2牲剃、共享數(shù)據(jù):多個(gè)線程共同操作的變量
3、同步監(jiān)視器:俗稱鎖垂寥,任何一個(gè)類的對(duì)象都可以充當(dāng)鎖颠黎,但是要保證多個(gè)線程必須共用一把鎖(舉例說(shuō)一個(gè)公共廁所,假如說(shuō)每個(gè)人都帶一把鎖的話那么其他人看不到究竟是否上鎖)滞项。
繼承Thread的方式因?yàn)槭且獛讉€(gè)線程就會(huì)創(chuàng)建出幾個(gè)對(duì)象狭归,所以同步監(jiān)視器不能用this(當(dāng)前對(duì)象),而反觀實(shí)現(xiàn)Runnable的方式文判,實(shí)際上只會(huì)有一個(gè)對(duì)象过椎,所以,可以用this同步監(jiān)視器戏仓。Thread可以用當(dāng)前類.class疚宇。類也是對(duì)象,當(dāng)前的類是一直不變的赏殃。

2敷待、同步方法

同步方法就是類似于上面代碼塊,同樣也需要把操作共享數(shù)據(jù)的代碼拿出來(lái)仁热,拿到一個(gè)方法里面榜揖,將方法用synchronized進(jìn)行修飾。同步方法仍然要涉及到同步監(jiān)視器抗蠢,只是不再需要顯示的聲明举哟。如果同步方法是非靜態(tài)方法,默認(rèn)的同步監(jiān)視器就是this迅矛,如果是靜態(tài)方法妨猩,默認(rèn)的同步監(jiān)視器是當(dāng)前類本身。

3秽褒、Lock方法

從JDK5.0開始壶硅,java提供了更強(qiáng)大的線程同步機(jī)制——通過(guò)顯式定義同步鎖對(duì)象來(lái)實(shí)現(xiàn)同步。同步鎖使用Lock對(duì)象充當(dāng)销斟。java.util.concurrent.locks.Lock接口是控制多個(gè)線程對(duì)共享資源進(jìn)行訪問(wèn)的工具森瘪。鎖提供了對(duì)共享資源的獨(dú)占訪問(wèn),每次只能有一個(gè)線程對(duì)Lock對(duì)象加鎖票堵,線程開始訪問(wèn)共享資源之前應(yīng)先獲得Lock對(duì)象扼睬。
ReentrantLock類實(shí)現(xiàn)了Lock,它擁有與synchronized相同的并發(fā)性和內(nèi)存語(yǔ)義,在實(shí)現(xiàn)線程安全的控制中窗宇,比較常用的是ReentrantLock措伐,可以顯式加鎖、釋放鎖军俊。

package com.xigu.concurrent;

import java.util.concurrent.locks.ReentrantLock;

public class Window implements Runnable {
    private ReentrantLock lock = new ReentrantLock();
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            //調(diào)用鎖定方法
            lock.lock();
            if (ticket > 0) {
                System.out.println("當(dāng)前線程:" + Thread.currentThread().getName() + ",剩下票數(shù):" + ticket);
                ticket--;
            } else {
                System.out.println("沒有票了");
                break;
            }
            //調(diào)用解鎖方法
            lock.unlock();
        }
    }
}

class test {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("線程1");
        t1.setName("線程2");
        t1.setName("線程3");

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

問(wèn)題:synchronized和lock的異同侥加?

相同:二者都可以解決線程安全問(wèn)題
不同:synchronized機(jī)制在執(zhí)行完相應(yīng)的同步代碼后,自動(dòng)釋放同步監(jiān)視器粪躬,lock需要手動(dòng)開啟同步lock()担败,同時(shí)結(jié)束同步需要手動(dòng)的實(shí)現(xiàn)unlock()。synchronized有代碼塊鎖和方法鎖镰官,lock只有代碼鎖提前。使用lock,jvm將花費(fèi)更少的時(shí)間來(lái)調(diào)度線程泳唠。

4狈网、優(yōu)先使用順序

Lock>同步代碼塊>同步方法

6-2帶來(lái)的問(wèn)題:

好處:同步的方式,解決了線程的安全問(wèn)題笨腥。
壞處:操作同步代碼時(shí)拓哺,只能有一個(gè)線程參與,其他線程等待脖母,想當(dāng)于是一個(gè)單線程的過(guò)程士鸥,效率低。還可能造成線程的死鎖

線程的死鎖問(wèn)題
死鎖:
不同的線程分別占用對(duì)方需要的同步資源不放棄谆级,都在等待對(duì)方放棄自己需要的同步資源础淤,就形成了線程的死鎖。
出現(xiàn)死鎖后哨苛,不會(huì)出現(xiàn)異常,不會(huì)出現(xiàn)提示币砂,只是所有的線程都處于阻塞狀態(tài)建峭,無(wú)法繼續(xù)。
解決方法:
專門的算法决摧、原則
盡量減少同步資源的定義
盡量避免嵌套同步

6-3亿蒸、并發(fā)的三大特性

  • 原子性

原子性是指在一個(gè)操作中cpu不可以在中途暫停然后再調(diào)度,即不被中斷操作掌桩,要不全部執(zhí)行完成边锁,要不都不執(zhí)行。就好比轉(zhuǎn)賬波岛,從賬戶A向賬戶B轉(zhuǎn)1000元茅坛,那么必然包括2個(gè)操作:從賬戶A減去1000元,往賬戶B加上1000元则拷。2個(gè)操作必須全部完成贡蓖。

image.png

1:將 count 從主存讀到工作內(nèi)存中的副本中
2:+1的運(yùn)算
3:將結(jié)果寫入工作內(nèi)存
4:將工作內(nèi)存的值刷回主存(什么時(shí)候刷入由操作系統(tǒng)決定曹鸠,不確定的)

程序中原子性指的是最小的操作單元,比如自增操作斥铺,它本身其實(shí)并不是原子性操作彻桃,分了3步的,包括讀取變量的原始值晾蜘、進(jìn)行加1操作邻眷、寫入工作內(nèi)存。所以在多線程中剔交,有可能一個(gè)線程還沒自增完肆饶,可能才執(zhí)行到第二部,另一個(gè)線程就已經(jīng)讀取了值省容,導(dǎo)致結(jié)果錯(cuò)誤抖拴。那如果我們能保證自增操作是一個(gè)原子性的操作,那么就能保證其他線程讀取到的一定是自增后的數(shù)據(jù)腥椒。關(guān)鍵字:synchronized

  • 可見性

當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí)阿宅,一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值笼蛛。若兩個(gè)線程在不同的cpu洒放,那么線程1改變了i的值還沒刷新到主存,線程2又使用了i滨砍,那么這個(gè)i值肯定還是之前的往湿,線程1對(duì)變量的修改線程沒看到這就是可見性問(wèn)題。

image.png

如果線程2改變了stop的值惋戏,線程1一定會(huì)停止嗎领追?不一定。當(dāng)線程2更改了stop變量的值之后响逢,但是還沒來(lái)得及寫入主存當(dāng)中绒窑,線程2轉(zhuǎn)去做其他事情了,那么線程1由于不知道線程2對(duì)stop變量的更改舔亭,因此還會(huì)一直循環(huán)下去些膨。關(guān)鍵字:volatile、synchronized钦铺、final

  • 有序性

虛擬機(jī)在進(jìn)行代碼編譯時(shí)订雾,對(duì)于那些改變順序之后不會(huì)對(duì)最終結(jié)果造成影響的代碼,虛擬機(jī)不一定會(huì)按照我們寫的代碼的順序來(lái)執(zhí)行矛洞,有可能將他們重排序洼哎。實(shí)際上,對(duì)于有些代碼進(jìn)行重排序之后,雖然對(duì)變量的值沒有造成影響谱净,但有可能會(huì)出現(xiàn)線程安全問(wèn)題窑邦。

image.png

write方法里的1和2做了重排序,線程1先對(duì)flag賦值為true壕探,隨后執(zhí)行到線程2冈钦,ret直接計(jì)算出結(jié)果,再到線程1李请,這時(shí)候a才賦值為2,很明顯遲了一步瞧筛。關(guān)鍵字:volatile、synchronized

volatile本身就包含了禁止指令重排序的語(yǔ)義导盅,而synchronized關(guān)鍵字是由 “一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則明確的较幌。
synchronized關(guān)鍵字同時(shí)滿足以上三種特性,但是volatile關(guān)鍵字不滿足原子性白翻。在某些情況下乍炉,volatile的同步機(jī)制的性能確實(shí)要優(yōu)于鎖(使用synchronized關(guān)鍵字或java.util.concurrent包里面的鎖),因?yàn)関olatile的總開銷要比鎖低滤馍。判斷使用volatile還是加鎖的唯一依據(jù)就是volatile的語(yǔ)義能否滿足使用的場(chǎng)景(原子性)

6-4岛琼、volatile

  1. 被volatile修飾的共享變量對(duì)所有線程總是可見的,也就是當(dāng)一個(gè)線程修改了一個(gè)被volatile修飾共享變量的值巢株,新值總是可以被其他線程立即得知槐瑞。如果線程2改變了stop的值,線程1一定會(huì)停止嗎阁苞?不一定困檩。當(dāng)線程2更改了stop變量的值之后,但是還沒來(lái)得及寫入主存當(dāng)中那槽,線程2轉(zhuǎn)去做其他事情了悼沿,那么線程1由于不知道線程2對(duì)stop變量的更改,因此還會(huì)一直循環(huán)下去骚灸。
image.png

2. 禁止指令重排序優(yōu)化糟趾。

image.png

使用了volatile修飾之后就變得不一樣了,不會(huì)出現(xiàn)上面有序性的那種問(wèn)題逢唤。

  • 第一:使用volatile關(guān)鍵字會(huì)強(qiáng)制將修改的值立即寫入主存;
  • 第二:使用volatile關(guān)鍵字的話涤浇,當(dāng)線程2進(jìn)行修改時(shí)鳖藕,會(huì)導(dǎo)致線程1的工作內(nèi)存中緩存變量stop的緩存行無(wú)效(反映到硬件層的話,就是CPU的L1或者L2緩存中對(duì)應(yīng)的緩存行無(wú)效)只锭;
  • 第三:由于線程1的工作內(nèi)存中緩存變量stop的緩存行無(wú)效著恩,所以線程1再次讀取變量stop的值時(shí)會(huì)去主存讀取。inc++; 其實(shí)是兩個(gè)步驟,先加加喉誊,然后再賦值邀摆。不是原子性操作,所以volatile不能保證線程安全伍茄。

七栋盹、線程通信

1、涉及的常見方法

wait():一旦進(jìn)入此方法敷矫,當(dāng)前線程就會(huì)進(jìn)入阻塞狀態(tài)例获,并釋放同步監(jiān)視器
notify():一旦進(jìn)入此方法,就會(huì)喚醒wait的一個(gè)線程曹仗,如果多個(gè)線程wait榨汤,就會(huì)喚醒優(yōu)先級(jí)高的線程
notifyAll():一旦進(jìn)入此方法,就會(huì)喚醒所有wait線程

說(shuō)明:

1怎茫、這三個(gè)方法必須在同步代碼快或者同步方法中收壕。
2、這三個(gè)方法的調(diào)用者必須是同步代碼塊或者同步方法中的同步監(jiān)視器轨蛤,否則會(huì)出現(xiàn)IlegalMonitorStateException異常
3蜜宪、這三個(gè)方法是定義在Object類

面試題:wait和sleep方法的異同點(diǎn)?

相同點(diǎn):一旦執(zhí)行此方法俱萍,都可以使得當(dāng)前線程進(jìn)入阻塞狀態(tài)
不同點(diǎn):
1)兩個(gè)方法聲明的位置不同端壳,Thread中聲明sleep(),Object類中聲明wait()
2)調(diào)用的要求不同:sleep()可以在任何需要的場(chǎng)景下調(diào)用,wait()必須在同步代碼塊枪蘑、同步方法中使用损谦。
3)關(guān)于是否釋放同步監(jiān)視器,如果兩個(gè)方法都使用在同步代碼塊或同步方法中岳颇,sleep()不釋放鎖照捡,wait()釋放鎖。

八话侧、多線程8鎖

  • 1栗精、標(biāo)準(zhǔn)訪問(wèn),先打印郵件還是先發(fā)送短信
  • 2瞻鹏、郵件方法暫停4秒鐘悲立,先打印郵件還是hello?
  • 3、新增一個(gè)普通方法hello()新博,先打印郵件還是hello()薪夕?
  • 4、兩部手機(jī)赫悄,請(qǐng)問(wèn)先答應(yīng)郵件還是短信
  • 5原献、兩個(gè)靜態(tài)同步方法馏慨,同一部手機(jī),先打印郵件還是短信
  • 6姑隅、兩個(gè)靜態(tài)同步方法写隶,兩部手機(jī),先打印郵件還是短信
  • 7讲仰、一個(gè)普通同步方法慕趴,一個(gè)靜態(tài)同步方法,一部手機(jī)叮盘,先郵件還是短信
  • 8秩贰、一個(gè)普通同步方法,一個(gè)靜態(tài)同步方法柔吼,兩部手機(jī)毒费,先郵件還是短信
package com.xigu.concurrent;

class Phone {
    public synchronized void sendMail() {
        System.out.println(Thread.currentThread().getName() + ":sendMail");
    }

    public synchronized void sendEMS() {
        System.out.println(Thread.currentThread().getName() + ":sendEMS");
    }

    public void sayHello() {
        System.out.println(Thread.currentThread().getName() + ":hello");
    }

}

public class Lock8 {
    public static void main(String[] args) throws Exception {
        Phone phone1 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendMail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        //為了達(dá)到A先啟動(dòng),B后啟動(dòng)的效果
        Thread.sleep(200);

        new Thread(() -> {
            try {
                phone1.sendEMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

1愈魏、先打印郵件觅玻,然后發(fā)送短信
一個(gè)對(duì)象里面如果有多個(gè)synchronized方法,某一時(shí)刻內(nèi)培漏,只要有一個(gè)線程調(diào)用了其中的一個(gè)synchronized方法溪厘,其他線程只能等待。換句話說(shuō)牌柄,只能有唯一一個(gè)線程去訪問(wèn)這些synchronized方法畸悬。因?yàn)殒i住的是對(duì)象this,被鎖定后珊佣,其他線程都不能進(jìn)入到當(dāng)前對(duì)象的其他synchronized方法蹋宦。
2、3咒锻、先hello冷冗,4s后再打印郵件
加個(gè)普通方法后發(fā)現(xiàn)和同步鎖無(wú)關(guān)。
4惑艇、先打印SMS方法蒿辙,大約4s后打印郵件方法。
A線程先啟動(dòng)滨巴,0.2s后B線程啟動(dòng)思灌,A調(diào)用sendMail方法,然后陷入4s睡眠恭取。B線程后來(lái)居上泰偿,打印SMS方法,然后打印郵件方法秽荤。因?yàn)閾Q成兩個(gè)對(duì)象后甜奄,不再是同一把鎖了,就好比用兩部手機(jī)窃款,一部在發(fā)郵件课兄,一部在發(fā)短信,互不影響晨继。

public class Lock8 {
    public static void main(String[] args) throws Exception {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendMail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        //為了達(dá)到A先啟動(dòng)烟阐,B后啟動(dòng)的效果
        Thread.sleep(200);

        new Thread(() -> {
            try {
                phone2.sendEMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

5、6紊扬、先睡眠大概4s蜒茄,然后打印郵件,再發(fā)送短信
第五題和第六題都是一樣的答案餐屎,因?yàn)殪o態(tài)方法的鎖是類class檀葛,類被鎖住了,下一個(gè)線程想要拿到鎖還是得等到當(dāng)前線程將鎖釋放才行腹缩。

package com.xigu.concurrent;


class Phone {
    public static synchronized void sendMail() throws Exception{
        Thread.sleep(4000);
        System.out.println(Thread.currentThread().getName() + ":sendMail");
    }

    public static synchronized void sendEMS() {
        System.out.println(Thread.currentThread().getName() + ":sendEMS");
    }

    public void sayHello() {
        System.out.println(Thread.currentThread().getName() + ":hello");
    }

}

public class Lock8 {
    public static void main(String[] args) throws Exception {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendMail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        //為了達(dá)到A先啟動(dòng)屿聋,B后啟動(dòng)的效果
        Thread.sleep(200);

        new Thread(() -> {
            try {
                phone1.sendEMS();
                //phone2.sendEMS();

            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

7、8藏鹊、先打印EMS润讥,大概4s后打印mail()
弄懂了上面,就較為簡(jiǎn)單了盘寡,看鎖的對(duì)象是什么楚殿,能不能拿到鎖就行了

package com.xigu.concurrent;


class Phone {
    public synchronized void sendMail() throws Exception {
        Thread.sleep(4000);
        System.out.println(Thread.currentThread().getName() + ":sendMail");
    }

    public static synchronized void sendEMS() {
        System.out.println(Thread.currentThread().getName() + ":sendEMS");
    }

    public void sayHello() {
        System.out.println(Thread.currentThread().getName() + ":hello");
    }

}

public class Lock8 {
    public static void main(String[] args) throws Exception {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone1.sendMail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        //為了達(dá)到A先啟動(dòng),B后啟動(dòng)的效果
        Thread.sleep(200);

        new Thread(() -> {
            try {
                phone1.sendEMS();
                //phone2.sendEMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

總結(jié):
synchronized實(shí)現(xiàn)同步的基礎(chǔ):java中的每一個(gè)對(duì)象都可以作為鎖
具體有三種形式:
普通同步方法:鎖的是當(dāng)前實(shí)例對(duì)象
靜態(tài)同步方法:鎖的是當(dāng)前類class的對(duì)象
同步方法塊:鎖的是synchronized括號(hào)里面配置的對(duì)象

如果一個(gè)實(shí)例對(duì)象的非靜態(tài)同步方法獲取鎖后竿痰,該實(shí)例對(duì)象的其他非靜態(tài)方法必須等待鎖的方法釋放鎖才能獲取到鎖脆粥,但是別的實(shí)例對(duì)象的非靜態(tài)同步方法因?yàn)楦搶?shí)例對(duì)象的非靜態(tài)同步方法用的是不同的鎖,所以不需要等該是對(duì)象已獲取到鎖的非靜態(tài)同步方法釋放鎖就可以獲取他們自己的鎖菇曲。

所有的靜態(tài)同步方法用的是同一把鎖——類對(duì)象本身冠绢。如果一個(gè)是靜態(tài)同步方法,一個(gè)是非靜態(tài)同步方法常潮,那么這兩把鎖是不同的弟胀,所以二者之間是不會(huì)有競(jìng)態(tài)條件的。但是一旦一個(gè)靜態(tài)同步方法獲取到鎖之后喊式,其他靜態(tài)同步方法都必須要等該方法的鎖釋放后才能獲取到鎖孵户,而不管他們是不是同一個(gè)實(shí)例對(duì)象。只要他們是同一個(gè)類的實(shí)例對(duì)象岔留,就都要遵循夏哭!當(dāng)一個(gè)線程試圖訪問(wèn)同步代碼塊時(shí),它首先必須獲取到鎖献联,退出或拋出異常時(shí)必須釋放鎖竖配。

九何址、多線程練習(xí)

題目一:3個(gè)售票員賣出30張票

思路:線程一>操作(對(duì)外暴露的方法)一> 資源類
傳統(tǒng)寫法:線程,操作(saleTicket方法)进胯,資源類(Ticket )

class Ticket {

    private int ticket = 30;

    public synchronized void saleTicket() {
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "賣掉了一張票,還剩" + ticket--);
        }
    }
}

public class SaleTicket {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //Thread的構(gòu)造方法用爪,Thread(Runnable target,String name)
        //如下傳入一個(gè)匿名內(nèi)部類+name
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticket.saleTicket();
                }
            }
        }, "售票員A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticket.saleTicket();
                }
            }
        }, "售票員B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    ticket.saleTicket();
                }
            }
        }, "售票員C").start();
    }
}

新寫法:使用了LambdaExpress的寫法

class Ticket {

    private int ticket = 100;
    private Lock lock = new ReentrantLock();
    public void saleTicket() {
        lock.lock();
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "賣掉了一張票,還剩" + ticket--);
        }
        lock.unlock();
    }
}

public class SaleTicket {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        }, "售票員A").start();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        }, "售票員B").start();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                ticket.saleTicket();
            }
        }, "售票員C").start();
    }
}

題目二:生產(chǎn)者消費(fèi)者問(wèn)題,原始值為0胁镐,一個(gè)加1一個(gè)減1偎血,來(lái)10輪

思路:判斷一>干活一>通知
注意:永遠(yuǎn)在while{}中使用wait()方法

class Product {
    private int number = 0;

    //生產(chǎn)
    public synchronized void produce() throws InterruptedException {
        //判斷
        while (number != 0) {
            this.wait();
        }
        //干活
        number++;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了一件商品,當(dāng)前剩余商品" + number);
        //通知
        this.notifyAll();
    }


    //消費(fèi)
    public synchronized void reduce() throws Exception {
        //判斷
        while (number == 0) {
            this.wait();
        }
        //干活
        number--;
        System.out.println(Thread.currentThread().getName() + "消費(fèi)了一件商品盯漂,當(dāng)前剩余商品" + number);
        //通知
        this.notifyAll();
    }
}

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

        Product product = new Product();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "來(lái)了");
                    product.produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生產(chǎn)者A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "來(lái)了");
                    product.reduce();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "消費(fèi)者B").start();
    }
}

題目三:生產(chǎn)者/消費(fèi)者問(wèn)題

生產(chǎn)者(Productor)將產(chǎn)品交給店員(Clerk)颇玷,而消費(fèi)者(Customer)從店員處取走產(chǎn)品,店員一次只能持有固定數(shù)量的產(chǎn)品(比如:20)就缆,如果生產(chǎn)者試圖生產(chǎn)更多的產(chǎn)品帖渠,店員會(huì)叫生產(chǎn)者停一下,如果店中有空位放產(chǎn)品了再通知生產(chǎn)者繼續(xù)生產(chǎn)竭宰;如果店中沒有產(chǎn)品了阿弃,店員會(huì)告訴消費(fèi)者等一下,如果店中有產(chǎn)品了再通知消費(fèi)者來(lái)取走產(chǎn)品
分析:
1羞延、是否是多線程問(wèn)題渣淳?是,生產(chǎn)者線程,消費(fèi)者線程
2、是否有共享數(shù)據(jù)邑跪?是理肺,店員/產(chǎn)品
3、如何解決線程安全問(wèn)題?同步機(jī)制,三種方法。
4.是否涉及多線程的通信旁赊?是

class Product {
    private int number = 0;

    //生產(chǎn)
    public synchronized void Productor() throws InterruptedException {
        //判斷
        while (number >= 20) {
            System.out.println("店員通知停止生產(chǎn)");
            this.wait();
        }
        //執(zhí)行
        number++;
        System.out.println(Thread.currentThread().getName() + "生產(chǎn)了商品,還剩" + number);
        //通知
        this.notifyAll();
    }

    //消費(fèi)
    public synchronized void Customer() throws InterruptedException {
        //判斷
        while (number == 0) {
            this.notifyAll();
            System.out.println("店員通知開始生產(chǎn)");
            this.wait();
        }
        //執(zhí)行
        number--;
        System.out.println(Thread.currentThread().getName() + "消費(fèi)了商品椅野,還剩" + number);
    }
}

public class ProductorAndCustomer {
    public static void main(String[] args) {
        Product product = new Product();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    product.Productor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生產(chǎn)者").start();

        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    product.Customer();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消費(fèi)者A").start();

    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末终畅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竟闪,更是在濱河造成了極大的恐慌离福,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炼蛤,死亡現(xiàn)場(chǎng)離奇詭異妖爷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)理朋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門絮识,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)绿聘,“玉大人,你說(shuō)我怎么就攤上這事次舌⌒庇眩” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵垃它,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我烹看,道長(zhǎng)国拇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任惯殊,我火速辦了婚禮酱吝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘土思。我一直安慰自己务热,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布己儒。 她就那樣靜靜地躺著崎岂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闪湾。 梳的紋絲不亂的頭發(fā)上冲甘,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音途样,去河邊找鬼江醇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛何暇,可吹牛的內(nèi)容都是我干的陶夜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼裆站,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼条辟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起宏胯,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捂贿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胳嘲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厂僧,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年了牛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颜屠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辰妙。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖甫窟,靈堂內(nèi)的尸體忽然破棺而出密浑,到底是詐尸還是另有隱情,我是刑警寧澤粗井,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布尔破,位于F島的核電站,受9級(jí)特大地震影響浇衬,放射性物質(zhì)發(fā)生泄漏懒构。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一耘擂、第九天 我趴在偏房一處隱蔽的房頂上張望胆剧。 院中可真熱鬧,春花似錦醉冤、人聲如沸秩霍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铃绒。三九已至,卻和暖如春螺捐,著一層夾襖步出監(jiān)牢的瞬間匿垄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工归粉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椿疗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓糠悼,卻偏偏與公主長(zhǎng)得像届榄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倔喂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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