Java線程創(chuàng)建(賣票),線程同步(賣包子)的實現(xiàn)示例
1.線程兩種創(chuàng)建方式:new Thread(new Runnable() {})

如下FileOutputStream源碼中拋出異常,為了讓寫代碼人自己寫try catch異常提示信息。

package com.itheim07.thread;
/*
* 進程和線程
* 1. 進程 : 航空母艦(資源: 燃油 彈藥)
* 2. 線程 : 艦載機
* 一個軟件運行: 一個軍事活動, 必須有一艘航母出去,但執(zhí)行具體任務的是航母上的艦載機
* 一個軟件運行,至少一個進程, 一個進程中至少一個線程。谷歌瀏覽器是多進程,進程多了,占用資源多,速度快
*
* cpu: 4核 8線程。線程要運行,需要cpu授予執(zhí)行權(指揮室),指揮室可以同時調(diào)度8架 飛機
* 1. 并行 : 同一時間,同時執(zhí)行 (并行只能8線程)
* 2. 并發(fā) : 同一段時間, 實際上是交替執(zhí)行, 速度快的時候看起來像是同時執(zhí)行(頻率快)(常見: 并發(fā)1800線程)
*
* cpu調(diào)度算法(并發(fā))
* 1. 分時調(diào)度 : 1800s, 每個線程1s
* 2. 搶占式調(diào)度 : 按照線程優(yōu)先級進行分配, 優(yōu)先級高(可以自己設置)一般就分配的多(隨機性強) java
*
* 為什么需要多線程?
* 1. 默認java代碼有兩個線程
* 1. main方法線程 : 主線程
* 2. GC線程(jvm使用的,我們無法調(diào)度)
* 2. 一個線程可用, 有什么局限性?只能做一件事
* 3. 如果想要同時執(zhí)行多個任務 -> 多線程
*/
public class ThreadDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("播放音樂...");
}
}
}).start(); //.start()不能改成.run()
boolean result = true;
while(result){
System.out.println("下載電影...");
}
/* while(result){ //雖然騙了編譯器,但還是不能執(zhí)行到這里
System.out.println("播放音樂...");
}*/
}
}
如下線程第一種創(chuàng)建方式。

package com.itheima01.thread;
/*
Thread:1. start() : 啟動線程,jvm會創(chuàng)建線程,并調(diào)用run方法
2. static Thread currentThread(),返回對當前正在執(zhí)行的線程對象的引用。
3. String getName() : 獲取線程名稱
!!! Thread.currentThread().getName() : 獲取當前線程名稱
線程默認命名規(guī)則:1. main線程 : main
2. 子線程(main線程創(chuàng)建的線程) : static int number;static被共享
Thread-0 , 1, 2 ...
*/
public class ThreadDemo02 {
public static void main(String[] args) {
// Thread thread = Thread.currentThread();
// String name = thread.getName();
// System.out.println(name); // main
//下面一行等同于上面
System.out.println("主:" + Thread.currentThread().getName());
YourThread yt = new YourThread();
yt.start(); //子:Thread-0
YourThread yt2 = new YourThread();
yt.run(); //子:main。 因為子線程YourThread還未執(zhí)行起飛 ,被main飛機拖著走
YourThread yt3 = new YourThread();
yt3.start(); //子:Thread-2。 不是Thread-1是因為yt2未起飛但依舊new了yt2
// Person p = new Person(); //執(zhí)行空參構造
// System.out.println(p.number); //0
// Person p2 = new Person();
// System.out.println(p2.number); //1
}
}
class YourThread extends Thread{
@Override
public void run() {
System.out.println("子:" + Thread.currentThread().getName());
}
}
class Person{
static int number=-1;
public Person(){
number++;
}
}
package com.itheima02.runnable;
/*
* 線程第二種創(chuàng)建方式: 1. 聲明實現(xiàn) Runnable 接口的類。
* 2. 該類然后實現(xiàn) run 方法。
* 3. 然后可以分配該類的實例,在創(chuàng)建 Thread 時作為一個參數(shù)來傳遞并啟動。
* Thread(Runnable target)
*/
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); // 分配該類的實例
Thread t = new Thread(mr);
t.start(); //Thread-0
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
package com.itheima02.runnable;
//用匿名內(nèi)部類簡化上面代碼
public class RunnableDemo02 {
public static void main(String[] args) {
/* Runnable mr = new Runnable(){ //用接口名Runnable代替子類類名,匿名對象。
//不用再寫class MyRunnable implements Runnable{},Runnable mr = new MyRunable(); 向上轉型
@Override
public void run() { //new一個接口()再{},是new這個接口的子類對象
System.out.println(Thread.currentThread().getName());
}
};
Thread t = new Thread(mr);
t.start();
// new Thread(mr).start(); */
//111111111111111111111111111111111111111111111111111111111111111111111111111111
new Thread(new Runnable() {
@Override
public void run() { //主要關注run
System.out.println(Thread.currentThread().getName());
}
}).start();
//new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
}
}
2.賣票:原子性
package com.itheima03.ticket;
/*
* 需求假設某航空公司有三個窗口發(fā)售某日某次航班的100張票,100張票可以作為共享資源,三個售票窗口需要創(chuàng)建三個線程
* 好處: 多線程執(zhí)行同一任務,比較快
* 1. 程序(單線程) , 并發(fā)1600線程, cpu分配執(zhí)行權: 1/1600
* 2. 程序(多線程 100) , 并發(fā)1700, cpu分配給我們的程序執(zhí)行權更多:1/17
* 注意: 線程不是越多越好(線程本身很占內(nèi)存, 慢。票數(shù)不多不需要用多線程)
*/
public class TicketDemo01 {
public static void main(String[] args) {
MyWindow mw1 = new MyWindow(); //堆中開一塊空間,不加static,number=100進堆
mw1.setName("窗口壹");
MyWindow mw2 = new MyWindow(); //同上
mw2.setName("窗口222");
MyWindow mw3 = new MyWindow(); //同上
mw3.setName("窗口三三三");
mw1.start();
mw2.start();
mw3.start();
}
}
//11111111111111111111111111111111111111111111111111111111111111111111111111111
class MyWindow extends Thread{
static int number = 100; //去掉static,每創(chuàng)建一個MyWindow窗口在堆里開辟一塊空間,三個窗口各賣100張
@Override
public void run() {
while(number > 0){
System.out.println(Thread.currentThread().getName() + "正在賣出第" + number + "張票");
number--;
}
}
}
/* * 兩種線程創(chuàng)建方式: 1. 繼承Thread * 2. 實現(xiàn)Runnbale * 如上第二種方案會更好一些,不需要加static,因為只new了一個對象 * 1. 實現(xiàn)接口,而不是繼承類(擴展性更強) 接口可以多實現(xiàn),但是類只能單繼承(MyWindow繼承Thread后,就不能繼承另外的類。MyTask可以繼承其他類,實現(xiàn)其他接口) * 2. 更符合 面向對象 (高內(nèi)聚,低耦合:線程獨立,和業(yè)務代碼MyTask分離,傳入賣豬肉任務也行)。封裝(各干各的,有必要再進行合作) */
如下線程同步問題分析:兩種創(chuàng)建方式3個窗口都總賣出102張票,而不是100張。原因:三個窗口同時卡在打印正在賣出第100張票。解決:t1在賣第100張票時,cpu可能會切到t3和t2,可以控制t2和t3不動,等t1的number- -完再動。

3.線程同步:synchronized關鍵字,Lock接口,ThreadLocal
package com.itheima04.synchronizedd;
import java.io.IOException;
/*
* 1. 代碼塊
* synchronized(鎖對象){
* 代碼A
* }
* 1. 鎖對象可以是任意對象,但必須唯一
* 2. 同步代碼塊中的 代碼A 同一時間,只允許一個線程執(zhí)行
* 使用同步鎖的注意點:1. 在保證業(yè)務邏輯可用的情況,同步鎖加的范圍越小越好
*
* 2. 鎖對象必須唯一:<1> 如果能保證當前對象唯一,this也可以作為鎖對象 (更節(jié)省內(nèi)存)
* <2> 當前類名.class(最好的鎖對象) -> Class對象(一個類被加載,在內(nèi)存都會有一個Class對象) 反射
*/
public class TicketDemo02 {
public static void main(String[] args) {
MyTask mt = new MyTask();
//上面只new了一個,可以用this
Thread t1 = new Thread(mt);
t1.setName("窗口壹");
Thread t2 = new Thread(mt);
t2.setName("窗口222");
Thread t3 = new Thread(mt);
t3.setName("窗口三三三");
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
int number = 100;
// Object obj = new Object(); //鎖對象
@Override
public void run() {
while(number > 0){
//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
synchronized(MyTask.class){ //MyTask.class也可以換成this
if(number <= 0){
break; //跳出while大循環(huán)
}
System.out.println(Thread.currentThread().getName() + "正在賣出第" + number + "張票");
number--;
}
//111111111111111111111111111111111111111111111111111111111111111111111111111111111111
//這邊只能try catch不能throws,原因:父類Runnable中run方法沒有聲明拋出編譯異常,所以子類也不能throws
try {
Thread.sleep(1); //線程啥事也不干,暫停1ms,cpu有空閑切換其他線程
} catch (InterruptedException e) {
e.printStackTrace();
}
} //while里
}
}

如下t2賣到0張時出while,而t1和t3還在while里,此時number=0,所以變?yōu)?和-1。

如下把synchronized拖到外面也不行。

如下加if(number <= 0),沒有加浪費時間代碼,所以看不到交替效果,但不會出現(xiàn)0和-1。

obj是鎖對象即鑰匙,如下鑰匙不能進run方法(每個線程一把即三把鑰匙了),只能在成員位置。

用this,不用new object(),可以節(jié)約內(nèi)存。

package com.itheima05.method;
/*
* synchronized 方法(同步方法)
* 1. 語法 : 方法聲明 + synchronized
* 2. 同步方法有沒有鎖對象? 有
* 1. 普通方法: 是this
* 2. 靜態(tài)方法: 靜態(tài)不能和對象(this)有關。 是當前類名.class
*/
public class TicketDemo02 {
public static void main(String[] args) {
MyTask mt = new MyTask();
Thread t1 = new Thread(mt);
t1.setName("窗口壹");
Thread t2 = new Thread(mt);
t2.setName("窗口222");
Thread t3 = new Thread(mt);
t3.setName("窗口三三三");
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
static int number = 100;
@Override
public void run() {
while(number > 0){
method(); //非靜態(tài)方法可以調(diào)用靜態(tài)方法
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static synchronized void method() { //靜態(tài)方法不能和對象關鍵字如this相關 //同步方法效果 等價于 同步代碼塊
if(number <= 0){
return; //break只能寫在循環(huán)和switch里
}
System.out.println(Thread.currentThread().getName() + "正在賣出第" + number + "張票");
number--;
}
}
package com.itheima06.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* Lock接口: 1. 實現(xiàn)類 ReentrantLock
* 2. lock() 獲取鎖(獲取鑰匙)
* 3. unlock() 釋放鎖 (還鑰匙)
*/
public class TicketDemo02 {
public static void main(String[] args) {
MyTask mt = new MyTask();
Thread t1 = new Thread(mt);
t1.setName("窗口壹");
Thread t2 = new Thread(mt);
t2.setName("窗口222");
Thread t3 = new Thread(mt);
t3.setName("窗口三三三");
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
int number = 100;
Lock lock = new ReentrantLock(); //創(chuàng)建lock對象
@Override
public void run() {
while(number > 0){
//1111111111111111111111111111111111111111111111111111111111111111111111111
lock.lock();
if(number <= 0){
// System.out.println(Thread.currentThread().getName());
lock.unlock(); // 注意: lock提供了鎖的可視化操作(線程執(zhí)行結束,要記得手動釋放。廁所上完不能帶走鑰匙)//同步代碼塊return或break后是jvm自動釋放鎖。//這里不加lock.unlock()程序停不下來。
break;
}
System.out.println(Thread.currentThread().getName() + "正在賣出第" + number + "張票");
number--;
lock.unlock();
}
}
}
如下ThreadLocal相當于一個map,key就是當前的線程,value就是需要存儲的對象。

t1(…,User),如下情況可將User放入ThreadLocal中,每次通過.get拿到線程的User。


4.賣包子:wait,notify
package com.itheima07.bz;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
// obj.wait(); //IllegalMonitorStateException : 非法的監(jiān)視狀態(tài)異常,因為.wait()必須鎖對象調(diào)用如下
synchronized (obj){ //對象變成鎖對象
obj.wait(); //不會報錯,一直等待。在鎖對象中
}
}
}
如下兩個方法wait和notify不是給線程調(diào)用的,而是給鎖對象【鎖對象可以是任意對象】調(diào)用的如上所示。BaoZi只能一個線程對其操作。

package com.itheima07.bz;
public class BaoZi {
boolean isHave=false; //默認沒有包子
}
package com.itheima07.bz;
public class BaoziPu extends Thread {
BaoZi bz;
public BaoziPu(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
while(true){ //不停生產(chǎn)包子
//111111111111111111111111111111111111111111111111111111111111111111111111111111
synchronized (bz){ //加鎖: 同步代碼,生產(chǎn)包子時不讓別人打擾我。注意下面wait和notify
if(bz.isHave){
try {
bz.wait(); //包子鋪有包子就等待(此時吃貨正在吃包子)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子鋪生產(chǎn)包子..."); //沒包子
bz.isHave = true;
bz.notify(); //喚醒吃貨
}
} //while里
}
}
package com.itheima07.bz;
public class ChiHuo extends Thread{
BaoZi bz;
public ChiHuo(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
while(true){ //不停吃包子
//1111111111111111111111111111111111111111111111111111111111111111111111111111
synchronized (bz){
if(!bz.isHave){
try {
bz.wait(); //吃貨沒有包子就等待(此時包子鋪正在生產(chǎn)包子)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃貨吃包子"); //有包子
bz.isHave = false;
bz.notify(); //喚醒包子鋪
}
}
}
}
package com.itheima07.bz;
public class BzDemo {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
BaoziPu bzp = new BaoziPu(bz); //和下面一行共同操作一個包子對象
ChiHuo ch = new ChiHuo(bz);
bzp.start();
ch.start();
}
}

如下第一次沒有包子,所以繞過2中if到1。運行完1后就有包子了,1時間很短,cpu不切換線程,切換了也沒用,因為2中syn…(bz)包子被鎖住,就算切換到吃貨線程進不去syn…(bz)里,所以1中notify喚不醒吃貨線程。
1和2都在sy…(bz)里,bzp線程bz.wait()【有3個好處】進入等待狀態(tài)即進入監(jiān)視隊列即等待包子被吃,吃貨線程的synchronized鎖被打開,有包子不會wait,執(zhí)行3。
一個線程wait把自己停下來放入堆(監(jiān)視隊列)中,來年開春,另一個線程中3叫我起來干活。2和3對應,1和4對應。3喚醒了2中wait,但2沒鑰匙(鎖)動不了(鬼壓床),鑰匙在吃貨手上,所以3往后4執(zhí)行釋放鎖,1234不停循環(huán)執(zhí)行。

生產(chǎn)消費者模型:用戶發(fā)請求來相當于包子鋪生產(chǎn)包子即生產(chǎn)者。服務器24小時開著相當于消費者一天24小時等包子吃。不會讓消費者線程空轉浪費cpu資源,所以沒包子設置消費者線程為wait狀態(tài)不占用cpu資源。
package com.atguigu.test14;
// 線程通信是用來解決生產(chǎn)者與消費者問題。
public class Test14 {
public static void main(String[] args) {
Workbench tai = new Workbench(); //相當于包子
Cook c = new Cook("崔志恒", tai); //生產(chǎn)者
Waiter w = new Waiter("翠花", tai); //消費者
c.start();
w.start();
}
}
//11111111111111111111111111111111111111111111111111111111111111111111111111
class Workbench{
private static final int MAX = 10; //假設工作臺上最多能夠放10盤
private int count; //count是共用的,要考慮線程安全
public synchronized void put(){ //同步方法,非靜態(tài)方法來說,鎖對象就是this //往工作臺上放一盤菜
if(count >= MAX){
try {
//生產(chǎn)者停下來,等待
wait();//默認是this.wait(),所以上面必須加鎖對象synchronized
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//上面是安全校驗
count++;
System.out.println(Thread.currentThread().getName() + "放了一盤菜,剩余:" + count);
this.notify(); // 包子/工作臺.notify() //喚醒消費者
}
//1111111111111111111111111111111111111111111111111111111111111111111111111111
public synchronized void take(){//從工作臺上取走一盤菜
if(count<=0){
try {
wait(); //工作臺沒有菜,消費者應該停下來
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//上面是安全校驗
count--;
System.out.println(Thread.currentThread().getName() + "取走一盤菜,剩余:" + count);
this.notify(); //喚醒生產(chǎn)者
}
}
//1111111111111111111111111111111111111111111111111111111111111111111111111
class Cook extends Thread{
private Workbench tai;
public Cook(String name, Workbench tai) {
super(name);
this.tai = tai;
}
public void run(){
while(true){
tai.put(); //封裝了
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//111111111111111111111111111111111111111111111111111111111111111111111
class Waiter extends Thread{
private Workbench tai;
public Waiter(String name, Workbench tai) {
super(name); //name屬性在父類中已聲明
this.tai = tai;
}
public void run(){
while(true){
tai.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如下一直交替運行,不停。

如下線程6態(tài):鎖就是鑰匙上廁所,限時等待就是sleep,記住下面三個紅色。

如下B進不去不執(zhí)行

到此這篇關于Java線程創(chuàng)建(賣票),線程同步(賣包子)的實現(xiàn)示例的文章就介紹到這了,更多相關Java線程創(chuàng)建同步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot測試之@SpringBootTest與MockMvc的實戰(zhàn)應用小結
本文將深入探討SpringBoot測試中兩個核心工具:@SpringBootTest注解與MockMvc測試框架的實戰(zhàn)應用,幫助開發(fā)者構建更穩(wěn)健的測試體系,提高代碼質量與可維護性,感興趣的朋友一起看看吧2025-03-03
Spring中@Autowired和@Qualifier注解的3個知識點小結
這篇文章主要介紹了Spring中@Autowired和@Qualifier注解的3個知識點小結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
IDEA 2020.2 +Gradle 6.6.1 + Spring Boot 2.3.4 創(chuàng)建多模塊項目的超詳細教程
這篇文章主要介紹了IDEA 2020.2 +Gradle 6.6.1 + Spring Boot 2.3.4 創(chuàng)建多模塊項目的教程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
IDEA下lombok安裝及找不到get,set的問題的解決方法
這篇文章主要介紹了IDEA下lombok安裝及找不到get,set的問題的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04
解決java maven項目找不到jconsole-1.8.0.jar和tools-1.8.0.jar包問題
這篇文章主要介紹了解決java maven項目找不到jconsole-1.8.0.jar和tools-1.8.0.jar包問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Java去重排序之Comparable與Comparator的使用及說明
這篇文章主要介紹了Java去重排序之Comparable與Comparator的使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04

