【JavaEE 初階(三)】多線程代碼案例

?博主主頁: 33的博客?
??文章專欄分類:JavaEE??
🚚我的代碼倉庫: 33的代碼倉庫🚚
🫵🫵🫵關注我帶你了解更多線程知識

在這里插入圖片描述

目錄

  • 1.前言
  • 2.單例模式
    • 2.1餓漢方式
    • 2.2餓漢方式
  • 3.阻塞隊列
    • 3.1概念
    • 3.2實現
  • 4.定時器
    • 4.1概念
    • 4.2實現
  • 5.線程池
    • 5.1概念
    • 5.2實現
  • 6.總結

1.前言

在開發過程中,我們會遇到很多經典的場景,針對這些經典場景,大佬們就提出了一些解決方案,我們只需要按照這個解決方案來進行代碼的編寫,這樣就不會寫得很差。

2.單例模式

我們先了解什么是設計模式,在開發過程中,我們會遇到很多經典的場景,針對這些經典場景,大佬們就提出了一些解決方案,按照這個方式進行編程,代碼就不會很差,就例如下期中的棋譜,如果按照棋譜下棋也是不會下很爛的。
所謂單例就是單個實例(對象),那么怎么保證一個類只有一個對象呢?我們需要通過一些編程技巧來達成這樣的效果。
在一個類的內部提供一個實例,把構造方法設置為private避免再構造出新的實例

2.1餓漢方式

餓漢方式:

class Singleton{private static Singleton instance=new Singleton();public static Singleton getsingleton(){return instance;}private  Singleton(){}
}
public class Demo17 {public static void main(String[] args) {Singleton s1=Singleton.getsingleton();//Singleton s2=new Singleton();}
}

上述代碼中,創建一個實例的時候在類加載的時候,太早就創建了,那可不可以晚一點呢?

2.2餓漢方式

懶漢方式:

class Singleton2{private static Singleton2 instance2=null;public static Singleton2 getInstance2(){if(instance2==null){instance2=new Singleton2();}return instance2;}private Singleton2(){};
}

但懶漢模式是不安全的,如下:
在這里插入圖片描述
這樣就創建了兩個實例,所以我們需要對讀取和修改進行加鎖操作

class Singleton3{private static Singleton3 instance3=null;Object lock=new Object();public static Singleton3 getInstance2(){synchronized (lock){if(instance3==null){instance3=new Singleton3();}}return instance3;}    
}

這樣就只有在調用getInstance2方法才會去創建對象,但是又引出了新的問題,其實是否創建對象,只需要在第一次調用這個方法的時候判斷,一旦創建好,以后都不用在再去判斷了,可這樣寫,每次調用這個方法都會去判斷,這樣就消耗不少資源。我們進行優化:

class Singleton3{private static Singleton3 instance3=null;Object lock=new Object();public static Singleton3 getInstance2(){if(instance3==null){synchronized (lock){if(instance3==null){instance3=new Singleton3();}}}return instance3;}private Singleton3(){};
}

大家以為到這兒,代碼完美了嗎?其實并不是,在new的時候可能會引起指令重排序問題,那么什么是指令重排序問題呢?指令重排序也是編譯器為了提高執行效率,做出的優化,在保持邏輯不變的前提下,可能對編譯器做出優化.
例如我們要去一個水果超市買香蕉、蘋果、火龍果、獼猴桃四種水果但它們在不同的展區。
優化前:
在這里插入圖片描述
優化后在這里插入圖片描述
在通常情況下,在單線程中,指令重排序,就能夠保證邏輯不變的情況下,把程序的效率提高,但在多線程中就不一定了,可能會誤判。
new操作是可能觸發指令重排序
new可以分為3步:
1.申請內存空間
2.在內存空間上構造對象(構造方法)
3.把內存地址復制給instance引用。
如果內存進程優化:
1.申請內存空間
2.把內存地址復制給instance引用。
3.在內存空間上構造對象(構造方法)
在這里插入圖片描述
那么該怎么解決這個問題呢?可以使用volatile讓其修飾instanse就可以保證,在修改instanse的時候就不會出現指令重排序問題。

class singleton{private static volatile  singleton instance=null;public static singleton getinstance(){if (instance==null){synchronized (singleton.class){if (instance==null){instance=new singleton();}}}return instance;}private singleton(){}  
}
public class Demo21 {public static void main(String[] args) {singleton s1=singleton.getinstance();}
}

這個時候才算真正的完成一個單例模式。

3.阻塞隊列

3.1概念

阻塞隊列也是多線程編程中比較常見的一種數據結構,它是一種特殊的隊列,它具有線程安全的特點,并且帶有阻塞特性。
阻塞隊列最大的意義就是用來實現“生產者消費者模型”
例如:一家人在一起包餃子,但是搟面杖只有一個,那么就指定一定人搟餃子皮就稱它為生產者,每搟一張皮,就放入圓盤中,其余人都包餃子稱為消費者,如果圓盤中沒有餃子皮了,消費者就要等待生產,如果圓盤中放滿了就要等待生產者就要等待消費。
那么為啥要引入“生產者消費者模型”呢?隊我們又有什么好處呢?

1.解耦合:就是降低兩個代碼塊的緊密程度
2.削峰填谷
那么在Java中,怎么實現阻塞隊列呢?在標準庫里,以及提供了線程的

 public static void main(String[] args) throws InterruptedException {BlockingDeque<Integer> deque=new LinkedBlockingDeque<>();deque.put(1);deque.put(2);deque.put(3);System.out.println(deque.take());System.out.println(deque.take());System.out.println(deque.take());System.out.println(deque.take());}

最后一次出隊列,隊列已經空了,所以就會阻塞:
在這里插入圖片描述

3.2實現

既然我們已經會使用阻塞隊列了,那我們能不能自己實現一下呢?我們底層可以采用循環數組來實現。

public class MyBlockingqueue {String[] arr=new String[20];private volatile int head=0;//后續中既會讀又會改,為了避免內存可見性+volatileprivate volatile int end=0;private volatile int size=0;public void put(String elem) throws InterruptedException {synchronized (this){if(end==arr.length){this.wait();return;}arr[end]=elem;end++;size++;this.notify();//喚醒因為隊列空導致的阻塞if(end==arr.length){end=0;}}}public String tack() throws InterruptedException {synchronized (this){if (size==0){this.wait();}String ret=arr[head];head++;size--;this.notify();//喚醒因為隊列滿導致的阻塞if(head==arr.length){head=0;}return ret;}}
}

在這里插入圖片描述

4.定時器

4.1概念

定時器也是日常開發中常見的組件,約定一個時間,時間到達后就會執行某個邏輯。在Java標準庫中,有一個線程的標準庫。

public static void main(String[] args) {Timer timer=new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000");}},1000);System.out.println("定時器打開");
}

主線程執行schdule方法時,此時就把任務放到了timer中,此時timer中也也包含一個線程,叫做掃描線程,一旦時間到達就會給改線程安排任務。那么我們能不能自己實現呢?

4.2實現

1.需要定義一個類來描述一個任務,這個任務需要包含時間,和實際任務。
2.需要有一個數據結構,把任務全部存到數據庫中
3.Timer中需要一個線程來描述任務是否到達時間
一個任務類:

lass  MyTimerTask implements Comparable<MyTimerTask>{private   Runnable runnable;private long time;public MyTimerTask(Runnable runnable,long delay){this.runnable=runnable;this.time=System.currentTimeMillis()+delay;}public long gettime(){return time;}public Runnable getrun(){return runnable;}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time-o.time);}
}

一個Timer類:

public class MyTimer {private PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();Object locker=new Object();public void schedule(Runnable runnable,long time ){synchronized (locker){queue.offer(new MyTimerTask(runnable,time));locker.notify();}}public MyTimer(){Thread t=new Thread(()->{while (true){try {synchronized (locker){while (queue.isEmpty()){locker.wait();}MyTimerTask task= queue.peek();long curenttime=System.currentTimeMillis();if (curenttime>=task.gettime()){task.getrun().run();queue.poll();}else {locker.wait(task.gettime()-curenttime);}}}catch (InterruptedException e){e.printStackTrace();}}});t.start();}
}

測試類:

class M{public static void main(String[] args) {MyTimer myTimer=new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("3000");}},3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("2000");}},2000);System.out.println("開始");}
}

5.線程池

5.1概念

池,這個詞,是計算機中一種比較重要的思想方法,很多地方都涉及到,比如內存池,進程池,連接池等等。
線程池,就是指在使用第一個線程的時候就把其他線程線程一并創建好,后續如果想要使用這個其他線程,就不必再重新創建新的線程,直接從線程池中回去即可。
那么為啥線程創建好放在池子里后續再從池子中取,比新建線程的效率更高呢?
從池子中取是純用戶態的操作,而創建線程是用戶態+內核態相互配合完成的。如果一段程序是在系統內核中執行的就是叫內核態,否則為用戶態。當創建線程時,就需要調用系統api,進入內核進入一系列操作,但操作系統內核不僅僅是給該線程提供服務,也要給其他線程提供服務,那么這個效率就是非常低的了。
在Java標準庫中,提供了寫好的線程池,直接用即可。

public static void main(String[] args) {ExecutorService service= Executors.newCachedThreadPool();}

線程池對象并不是直接new的,而是調用一個方法返回線程池對象Executors.newCachedThreadPool()稱為工廠模式。
通常情況下創建一個對象需要用new關鍵字,new關鍵字會觸發類的構造方法,但構造方法具有局限性,例如:在一個類中,我即能用笛卡爾坐標系來表示一個點,又能有極坐標的方法表示一個:

class point{//笛卡爾坐標public point(double x,double y){}//極坐標public point(double a,double b){}
}

但如果要在一個類中實現多個構造方法,那么就要保證構造方法的參數不同,或者是類型不同。為了解決構造方法的局限性,我們就使用工廠設計模式。
工廠設計模式就是指,用一個單獨的類,再使用靜態普通方法代替構造方法做的事情。

class PointFactory{public static Point MackXY(){Point p=new Point();.......return p;}public static Point MackAB(){Point p=new Point();.......return p;}
}

在構造線程池中也有多種方法:

 public static void main(String[] args) {//線程池是動態的,cache緩存用了之后不立即釋放ExecutorService service= Executors.newCachedThreadPool();//固定創建幾個線程ExecutorService service1=Executors.newFixedThreadPool(3);//相當于定時器,但不是一個掃描線程進程操作而是多個線程了ExecutorService service2=Executors.newScheduledThreadPool(4);//固定只有一個線程ExecutorService service3=Executors.newSingleThreadExecutor();}

上述多種方法都是對ThreadExecutor進行的封裝,這個類非常豐富,提供了很多參數,標準庫中上述多種方法實際給這個類填寫了不同的參數來構造線程。
在這里插入圖片描述
具體看最后一種構造方法,因為包含了前面三種

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

int corePoolSize:核心線程數
int maximumPoolSize:最大線程數
long keepAliveTime:非核心線程在終止之前等待新任務的最長時間
TimeUnit unit:時間單位
BlockingQueue workQueue:阻塞隊列,存放線程池的任務
ThreadFactory threadFactory:用于創建新線程的工廠。
RejectedExecutionHandler handler:線程拒絕策略

RejectedExecutionHandler handler:線程拒絕策略
一個線程池中,能容納的線程數目已經達到最大上限,繼續再添加將有不同的效果:有以下4種效果

1.ThreadPoolExecutor.AbortPolicy:線程池直接拋出異常
2ThreadPoolExecutor.CallerRunsPolicy:新添加的任務由添加任務的線程自己執行
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列中最老的任務
4.ThreadPoolExecutor.DiscardPolicy:丟棄當前新加的任務

5.2實現

public class MyThreadPool {//設置任務隊列BlockingDeque<Runnable> queue=new LinkedBlockingDeque<>();//任務放到隊列public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}//線程執行public MyThreadPool(int n) throws InterruptedException {for (int i=0;i<n;i++){Thread t=new Thread(()->{try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}});t.start();}}
}
class M{public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool=new MyThreadPool(4);for (int i=0;i<100;i++){int id=i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("i="+id);}});}}
}

6.總結

單例模式,阻塞隊列,定時器,線程池,是一些常用的多線程代碼,希望同學們能夠熟練掌握它們得使用方法,感興趣的同學也可以自己實現一下。

下期預告:多線程進階

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10058.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10058.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10058.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

支付寶小程序如何去除頁面下拉回彈

描述&#xff1a;支付寶小程序頁面下拉時會產生回彈&#xff0c;如果頁面上有拖拽功能&#xff0c;會有影響 解決方法&#xff1a; 頁面xx.config.js中設置&#xff1a;allowsBounceVertical: “NO” 官方文檔&#xff1a;https://opensupport.alipay.com/support/FAQ/7110b5d…

WT32-ETH01作為TCP Client進行通訊

目錄 模塊簡介WT32-ETH01作為TCP Client設置電腦作為TCP Server設置連接并進行通訊總結 模塊簡介 WT32-ETH01網關主要功能特點: 采用雙核Xtensa⑧32-bit LX6 MCU.集成SPI flash 32Mbit\ SRAM 520KB 支持TCP Server. TCP Client, UDP Server. UDP Client工作模式 支持串口、wif…

鴻蒙OpenHarmony技術:【Docker編譯環境】

Docker環境介紹 OpenHarmony為開發者提供了兩種Docker環境&#xff0c;以幫助開發者快速完成復雜的開發環境準備工作。兩種Docker環境及適用場景如下&#xff1a; 獨立Docker環境&#xff1a;適用于直接基于Ubuntu、Windows操作系統平臺進行版本編譯的場景。基于HPM的Docker環…

其他編程語言中調用 Python 腳本,如何設置Python腳本的相對路徑

import os# 假設 script_directory 是你的腳本所在的目錄 script_directory os.path.dirname(os.path.abspath(__file__))# 使用 os.path.join 來構建相對路徑 relative_path_to_image os.path.join(script_directory, 合并/figure_pic2.png)# 現在你可以使用這個相對路徑來加…

uni-app+vue3 +uni.connectSocket 使用websocket

前言 最近在uni-appvue3websocket實現聊天功能&#xff0c;在使用websocket還是遇到很多問題 這次因為是app手機應用&#xff0c;就沒有使用websocket對象&#xff0c;使用的是uni-app的uni.connectSocket 為了方便測試這次用的是node.js一個簡單的dom&#xff0c;來聯調模擬…

Apache Flume Agent內部原理

Apache Flume Agent內部原理 Apache Flume 是一個可擴展的、分布式的日志收集、聚合和傳輸系統。在 Flume 中&#xff0c;Agent 是一個獨立的進程&#xff0c;負責接收、傳輸和處理數據。Agent 內部包含多個組件&#xff0c;每個組件都有不同的功能和責任。 1. Source&#xff…

5個 Elasticsearch 核心組件

Elasticsearch 是一個基于 Lucene 的搜索引擎&#xff0c;它提供了分布式、高可用、多租戶的能力。Elasticsearch 的核心組件包括節點&#xff08;Node&#xff09;、集群&#xff08;Cluster&#xff09;、索引&#xff08;Index&#xff09;、分片&#xff08;Shard&#xff…

三下鄉社會實踐投稿攻略在這里

在當今信息爆炸的時代&#xff0c;如何讓自己的聲音被更多人聽到&#xff0c;成為許多人和企業所關心的問題。其中&#xff0c;向各大媒體網站投稿&#xff0c;成為了一種常見的宣傳方式。但是&#xff0c;如何投稿各大媒體網站&#xff1f;新聞媒體發文策略又有哪些呢&#xf…

Flutter Clipboard實現復制功能

Flutter內置了Clipboard 功能,可以幫助我們完成復制粘貼的功能,比如我們想把“hello flutter”復制到粘貼板,代碼如下: TextButton(onPressed: () async {await Clipboard.setData(ClipboardData(text: hello flutter)

基于SpringBoot設計模式之開端

文章目錄 前言引言開始 前言 為了更好的在項目中&#xff0c;能更加優雅的使用設計模式&#xff0c;比較針對性的解決我們的問題。我將在這個專欄詳細的描述23種設計模式&#xff0c;為了與時俱進&#xff0c;我打算通過springboot的形式將23種設計模式全部擼完&#xff01; 引…

光耦推薦—高速風筒方案中用到哪些光耦型號

高速風筒是現代生活中常見的電器設備&#xff0c;廣泛應用于家庭、商業和工業領域&#xff1b;光耦是一種能夠將輸入信號轉換成輸出信號的元器件&#xff0c;其作用在于將電氣信號轉換成光信號&#xff0c;從而實現電路的隔離和保護&#xff1b;采用光耦可實現對風機轉速和溫度…

【管理咨詢寶藏99】離散制造智能工廠戰略規劃方案

本報告首發于公號“管理咨詢寶藏”&#xff0c;如需閱讀完整版報告內容&#xff0c;請查閱公號“管理咨詢寶藏”。 【管理咨詢寶藏99】離散制造智能工廠戰略規劃方案 【格式】PDF版本 【關鍵詞】智能制造、先進制造業轉型、數字化轉型 【核心觀點】 - 推進EHS、品質一致性、生…

【無標題】QCC 308x 518x 517x增加usb voice 32k采樣率

QCC 308x 518x 517x增加usb voice 32k采樣率 diff --git a/adk/src/domains/audio/kymera/kymera_usb_voice.c b/adk/src/domains/audio/kymera/kymera_usb_voice.c index 6dd82061..532c4ad8 100755 --- a/adk/src/domains/audio/kymera/kymera_usb_voice.c +++ b/adk/src/dom…

Failed to start tomcat.service: Unit is not loaded properly: Bad message 如何解決?

錯誤 “Failed to start tomcat.service: Unit is not loaded properly: Bad message” 通常意味著的 tomcat.service systemd 配置文件存在語法錯誤或配置不正確。為了解決這個問題&#xff0c;一步步檢查和修正這個服務文件。 1. 檢查 tomcat.service 文件 首先&#xff0c…

CSS文字描邊,文字間隔,div自定義形狀切割

clip-path: polygon( 0 0, 68% 0, 100% 32%, 100% 100%, 0 100% );//這里切割出來是少一角的正方形 letter-spacing: 1vw; //文字間隔 -webkit-text-stroke: 1px #fff; //文字描邊1px uniapp微信小程序頂部導航欄設置透明&#xff0c;下拉改變透明度 onP…

Docker部署RabbitMQ集群(單服務器多端口)

rabbitmq.conf在mq1、mq2、mq3下 ####文件內容 loopback_users.guest false listeners.tcp.default 5672 cluster_formation.peer_discovery_backend rabbit_peer_discovery_classic_config cluster_formation.classic_config.nodes.1 rabbitmq1 cluster_formation.classi…

SQL注入(sqli-labs第一關)

sqli-labs第一關 方法一&#xff1a;手工注入 來到第一關&#xff0c;圖上說我們需要一個數字的參數 于是我們先手工注入?id1 and 11 跟?id1 and 12發現頁面沒有報錯 每張截圖上面頁面中有select查詢語句&#xff0c;這是我在第一關的源碼中加上了echo "$sql ";…

SSM【Spring SpringMVC Mybatis】——Mybatis(二)

如果對一些基礎理論感興趣可以看這一期&#x1f447; SSM【Spring SpringMVC Mybatis】——Mybatis 目錄 1、Mybatis中參數傳遞問題 1.1 單個普通參數 1.2 多個普通參數 1.3 命名參數 1.4 POJO參數 1.5 Map參數 1.6 Collection|List|Array等參數 2、Mybatis參數傳遞【#與…

STL——stack容器【棧】

stack基本概念&#xff1a; 概念&#xff1a; 是一種先進后出的數據結構&#xff0c;它只有一個出口 因為只有一端可以調用&#xff0c;所以棧不支持遍歷操作 棧的操作&#xff1a; 棧中進入數據稱為&#xff1a;入棧(push) 棧中彈出數據稱為&#xff1a;出棧(pop) 生活中…

C#編程模式之享元模式

創作背景&#xff1a;各位朋友&#xff0c;我們繼續學習C#的編程模式&#xff0c;本文主要介紹享元模式。享元模式是一種結構型設計模式&#xff0c;它主要用于減少創建對象的數量&#xff0c;從而提高程序性能。它通過共享對象的方式來減少內存的使用&#xff0c;特別是系統中…