最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

馬哥7期高端Go語言百萬并發(fā)高薪班2022最新end無秘

2022-11-23 16:34 作者:山觀那恭喜囧昂貴的  | 我要投稿


Java并發(fā)編程--多線程間的同步控制和通訊

馬哥7期高端Go語言百萬并發(fā)高薪班2022最新end無秘

download:https://www.zxit666.com/5066/

運用多線程并發(fā)處置,目的是為了讓程序更充沛天時用CPU ,好能加快程序的處置速度和用戶體驗。假如每個線程各自處置的局部互不相干,那真是極好的,我們在程序主線程要做的同步控制最多也就是等候幾個工作線程的執(zhí)行終了,假如不 Care 結果的話,連同步等候都能省去,主線程撒開手讓這些線程干就行了。
不過,理想還是很嚴酷的,大局部狀況下,多個線程是會有競爭操作同一個對象的狀況的,這個時分就會招致并發(fā)常見的一個問題--數(shù)據(jù)競爭(Data Racing)。
這篇文章我們就來討論一下這個并發(fā)招致的問題,以及多線程間停止同步控制和通訊的學問,本文大綱如下:

并發(fā)招致的Data Racing問題
怎樣了解這個問題呢,拿一個多個線程同時對累加器對象停止累加的例子來解釋吧。
package com.learnthread;

public class DataRacingTest {
public static void main(String[] args) throws InterruptedException {
final DataRacingTest test = new DataRacingTest();
// 創(chuàng)立兩個線程,執(zhí)行 add100000() 操作
// 創(chuàng)立Thread 實例時的 Runnable 接口完成,這里直接運用了 Lambda
Thread th1 = new Thread(()-> test.add100000());
Thread th2 = new Thread(()-> test.add100000());
// 啟動兩個線程
th1.start();
th2.start();
// 等候兩個線程執(zhí)行完畢
th1.join();
th2.join();
System.out.println(test.count);
}

private long count = 0;

// 想復現(xiàn) Data Racing,去掉這里的 synchronized
private void add100000() {
int idx = 0;
while(idx++ < 100000) {
count += 1;
}
}
}
復制代碼
上面這個例程,假如我們不啟動 th2 線程,只用 th1 一個線程停止累加操作的話結果是 100000。依照這個思想,假如我們啟動兩個線程那么最后累加的結果就應該是 200000。 但實踐上并不是,我們運轉(zhuǎn)一下上面的例程,得到的結果是:
168404

Process finished with exit code 0
復制代碼
當然這個在每個人的機器上的結果是不一樣的,而且也是有可能恰恰等于 200000,需求多運轉(zhuǎn)幾次,或者是多開幾個線程執(zhí)行累加,呈現(xiàn) Data Racing 的幾率才高。
程序呈現(xiàn) Data Racing 的現(xiàn)象,就意味著最終拿到的數(shù)據(jù)是不正確的。那么為了防止這個問題就需求經(jīng)過加鎖來處理了,讓同一時間只要持有鎖的線程才干對數(shù)據(jù)對象停止操作。當然針對簡單的運算、賦值等操作我們也能直接運用原子操作完成無鎖處理 Data Racing, 我們?yōu)榱耸纠銐蚝唵我锥排e了一個累加的例子,實踐上假如是一段業(yè)務邏輯操作的話,就只能運用加鎖來保證不會呈現(xiàn) Data Racing了。
加鎖,只是線程并發(fā)同步控制的一種,還有釋放鎖、喚醒線程、同步等候線程執(zhí)行終了等操作,下面我們會逐一停止學習。
同步控制--synchronized
開頭的那個例程,假如想防止 Data Racing,那么就需求加上同步鎖,讓同一個時間只能有一個線程操作數(shù)據(jù)對象。 針對我們的例程,我們只需求在 add100000 辦法的聲明中加上 synchronized 即可。
// 想復現(xiàn) Data Racing,去掉這里的 synchronized
private synchronized void add100000() {
int idx = 0;
while(idx++ < 100000) {
count += 1;
}
}
復制代碼
是不是很簡單,當然 synchronized 的用法遠不止這個,它能夠加在實例辦法、靜態(tài)辦法、代碼塊上,假如運用的不對,就不能正確地給需求同步鎖維護的對象加上鎖。
synchronized 是 Java 中的關鍵字,是應用鎖的機制來完成互斥同步的。
synchronized 能夠保證在同一個時辰,只要一個線程能夠執(zhí)行某個辦法或者某個代碼塊。
假如不需求 Lock 、讀寫鎖ReadWriteLock 所提供的高級同步特性,應該優(yōu)先思索運用synchronized 這種方式加鎖,主要緣由如下:


Java 自 1.6 版本以后,對 synchronized 做了大量的優(yōu)化,其性能曾經(jīng)與 JUC 包中的 Lock 、ReadWriteLock 根本上持平。從趨向來看,Java 將來仍將繼續(xù)優(yōu)化 synchronized ,而不是 ReentrantLock 。
ReentrantLock 是 Oracle JDK 的 API,在其他版本的 JDK 中不一定支持;而 synchronized 是 JVM 的內(nèi)置特性,一切 JDK 版本都提供支持。

synchronized 能夠應用在實例辦法、靜態(tài)辦法和代碼塊上:

用 synchronized 關鍵字修飾實例辦法,即為同步實例辦法,鎖是當前的實例對象。
用 synchronized 關鍵字修飾類的靜態(tài)辦法,即為同步靜態(tài)辦法,鎖是當前的類的 Class 對象。
假如把 synchronized 應用在代碼塊上,鎖是 synchronized 括號里配置的對象,synchronized(this) {..} 鎖就是代碼塊所在實例的對象,synchronized(類名.class) {...} ,鎖就是類的 Class 對象。

同步實例辦法和代碼塊
上面我們曾經(jīng)看過怎樣給實例辦法加 synchronized 讓它變成同步辦法了。下面我們看一下,synchronized 給實例辦法加鎖時,不能保證資源被同步鎖維護的例子。
class Account {
private int balance;
// 轉(zhuǎn)賬
synchronized void transfer(Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
復制代碼
在這段代碼中,臨界區(qū)內(nèi)有兩個資源,分別是轉(zhuǎn)出賬戶的余額 this.balance 和轉(zhuǎn)入賬戶的余額 target.balance,并且用的是一把實例對象的鎖。問題就出在 this 這把鎖上,this 這把鎖能夠維護本人的余額 this.balance,卻維護不了他人的余額 target.balance,就像你不能用自家的鎖來維護他人家的資產(chǎn)一個道理。
應該保證運用的鎖能維護一切應受維護資源。我們能夠運用Account.class 作為加鎖的對象。Account.class 是一切 Account 類的對象共享的,而且是 Java 虛擬機在加載 Account 類的時分創(chuàng)立的,保證了它的全局獨一性。
class Account {
private int balance;
// 轉(zhuǎn)賬
void transfer(Account target, int amt){
synchronized(Account.class) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
復制代碼
用 synchronized 給 Account.class 加鎖,這樣就保證出賬、入賬兩個 Account 對象在同步代碼塊里都能收到維護。
當然我們也能夠運用這筆轉(zhuǎn)賬的買賣對象作為加鎖的對象,保證只要這比買賣的兩個 Account 對象受維護,這樣就不會影響到其他轉(zhuǎn)賬買賣里的出賬、入賬 Account 對象了。
class Account {
private Trans trans;
private int balance;
private Account();
// 創(chuàng)立 Account 時傳入同一個 買賣對象作為 lock 對象
public Account(Trans trans) {
this.trans = trans;
}
// 轉(zhuǎn)賬
void transfer(Account target, int amt){
// 此處檢查一切對象共享的鎖
synchronized(trans) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}

經(jīng)過處理上面這個問題我們順道就把 synchronized 修飾同步代碼塊的學問點學了, 如今我們來看 synchronized 的最后一個用法--修飾同步靜態(tài)辦法。
同步靜態(tài)辦法
靜態(tài)辦法的同步是指,用 synchronized 修飾的靜態(tài)辦法,與運用所在類的 Class 對象完成的同步代碼塊,效果相似。由于在 JVM 中一個類只能對應一個類的 Class 對象,所以同時只允許一個線程執(zhí)行同一個類中的靜態(tài)同步辦法。
關于同一個類中的多個靜態(tài)同步辦法,持有鎖的線程能夠執(zhí)行每個類中的靜態(tài)同步辦法而無需等候。不論類中的哪個靜態(tài)同步辦法被調(diào)用,一個類只能由一個線程同時執(zhí)行。
package com.learnthread;

public class SynchronizedStatic implements Runnable {

private static final int MAX = 100000;

private static int count = 0;

public static void main(String[] args) throws InterruptedException {
SynchronizedStatic instance = new SynchronizedStatic();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
// 等候工作線程執(zhí)行完畢
t1.join();
t2.join();
System.out.println(count);
}

@Override
public void run() {
for (int i = 0; i < MAX; i++) {
increase();
}
}

/**
* synchronized 修飾靜態(tài)辦法
*/
public synchronized static void increase() {
count++;
}

}

復制代碼
線程掛起和喚醒
上面我們看了運用 synchronized 給對象加同步鎖,讓同一時間只要一個線程能操作臨界區(qū)的控制。接下來,我們看一下線程的掛起和喚醒,這兩個操作運用被線程勝利加鎖的對象的 wait 和 notify 辦法來完成,喚醒除了notify 外還有 notifyAll辦法用來喚醒一切線程。下面我們先看一下這幾個辦法的解釋。

wait - wait 會自動釋放當前線程占有的對象鎖,并懇求操作系統(tǒng)掛起當前線程,讓線程從 Running 狀態(tài)轉(zhuǎn)入 Waiting 狀態(tài),等候被 notify / notifyAll 來喚醒。假如沒有釋放鎖,那么其它線程就無法進入對象的同步辦法或者同步代碼塊中,那么就無法執(zhí)行 notify 或者 notifyAll 來喚醒掛起的線程,會形成死鎖。
notify - 喚醒一個正在 Waiting 狀態(tài)的線程,并讓它拿到對象鎖,詳細喚醒哪一個線程由 JVM 控制 。
notifyAll - 喚醒一切正在 Waiting 狀態(tài)的線程,接下來它們需求競爭對象鎖。

這里有兩點需求各位留意的中央, 第一個是 wait、notify、notifyAll 都是 Object 類中的辦法,而不是 Thread 類的。


由于 Object 是始祖類,是不是意味著一切類的對象都能調(diào)用這幾個辦法呢?是,也不是... 由于 wait、notify、notifyAll 只能用在 synchronized 辦法或者 synchronized 代碼塊中運用,否則會在運轉(zhuǎn)時拋出 IllegalMonitorStateException。換句話說,只要被 synchronized 加上鎖的對象,才干調(diào)用這三個辦法。
為什么 wait、notify、notifyAll 不定義在 Thread 類中?為什么 wait、notify、notifyAll 要配合 synchronized 運用?
了解為什么這么設計,需求理解幾個根本學問點:

每一個 Java 對象都有一個與之對應的監(jiān)視器(monitor)
每一個監(jiān)視器里面都有一個 對象鎖 、一個 等候隊列、一個 同步隊列

理解了以上概念,我們回過頭來了解前面兩個問題。
為什么這幾個辦法不定義在 Thread 中?

由于每個對象都具有對象鎖,讓當前線程等候某個對象鎖,自然應該基于這個對象(Object)來操作,而非運用當前線程(Thread)來操作。由于當前線程可能會等候多個線程釋放鎖,假如基于線程(Thread)來操作,就十分復雜了。

為什么 wait、notify、notifyAll 要配合 synchronized 運用?

假如調(diào)用某個對象的 wait 辦法,當前線程必需具有這個對象的對象鎖,因而調(diào)用 wait 辦法必需在 synchronized 辦法和 synchronized 代碼塊中。

下面看一個 wait、notify、notifyAll 的一個經(jīng)典運用案例,完成一個消費者、消費者形式:
package com.learnthread;

import java.util.PriorityQueue;

public class ThreadWaitNotifyDemo {

private static final int QUEUE_SIZE = 10;
private static final PriorityQueue queue = new PriorityQueue<>(QUEUE_SIZE);

public static void main(String[] args) {
new Producer("消費者A").start();
new Producer("消費者B").start();
new Consumer("消費者A").start();
new Consumer("消費者B").start();
}

static class Consumer extends Thread {

Consumer(String name) {
super(name);
}

@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == 0) {
try {
System.out.println("隊列空,等候數(shù)據(jù)");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notifyAll();
}
}
queue.poll(); // 每次移走隊首元素
queue.notifyAll();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 從隊列取走一個元素,隊列當前有:" + queue.size() + "個元素");
}
}
}
}

static class Producer extends Thread {

Producer(String name) {
super(name);
}

@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == QUEUE_SIZE) {
try {
System.out.println("隊列滿,等候有空余空間");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notifyAll();
}
}
queue.offer(1); // 每次插入一個元素
queue.notifyAll();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 向隊列取中插入一個元素,隊列當前有:" + queue.size() + "個元素");
}
}
}
}
}
復制代碼
上面的例程有兩個消費者和兩個消費者。消費者向隊列中放數(shù)據(jù),每次向隊列中放入數(shù)據(jù)后運用 notifyAll 喚醒消費者線程,當隊列滿后消費者會 wait 讓出線程,等候消費者取走數(shù)據(jù)后再被喚醒 (消費者取數(shù)據(jù)后也會調(diào)用 notifyAll )。同理消費者在隊列空后也會運用 wait 讓出線程,等候消費者向隊列中放入數(shù)據(jù)后被喚醒。
線程等候--join
與 wait 和 notify 辦法一樣,join 是另一種線程間同步機制。當我們調(diào)用線程對象 join 辦法時,調(diào)用線程會進入等候狀態(tài),它會不斷處于等候狀態(tài),直到被援用的線程執(zhí)行完畢。在上面的幾個例子中,我們曾經(jīng)運用過了 join 辦法
...
public static void main(String[] args) throws InterruptedException {
SynchronizedStatic instance = new SynchronizedStatic();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
// 等候工作線程執(zhí)行完畢
t1.join();
t2.join();
System.out.println(count);
}
復制代碼
這個例子里,主線程調(diào)用 t1 和 t2 的 join 辦法后,就會不斷等候,直到他們兩個執(zhí)行完畢。假如 t1 或者 t2 線程處置時間過長,調(diào)用它們 join 辦法的主線程將不斷等候,程序阻塞住。為了防止這些狀況,能夠運用能指定超時時間的重載版本的 join 辦法。
t2.join(1000); // 最長等候1s
復制代碼
假如援用的線程被中綴,join辦法也會返回。在這種狀況下,還會觸發(fā) InterruptedException。所以上面的main辦法為了演示便當,直接選擇拋出了 InterruptedException。
總結
同步控制的一大思緒就是加鎖,除了本問學習到的 sychronized 同步控制,Java 里還有 JUC 的可重入鎖、讀寫鎖這種加鎖方式,這個我們后續(xù)引見 JUC 的時分會給大家解說。
另外一種思緒是不加鎖,讓線程和線程之間盡量不要運用共享數(shù)據(jù),ThreadLocal 就是這種思緒,下篇我們引見 Java 的線程本地存儲 -- ThreadLocal。

馬哥7期高端Go語言百萬并發(fā)高薪班2022最新end無秘的評論 (共 條)

分享到微博請遵守國家法律
安泽县| 河曲县| 绥德县| 涿州市| 会昌县| 嵩明县| 外汇| 垣曲县| 桐梓县| 额敏县| 台州市| 衡南县| 东乌珠穆沁旗| 沛县| 东乡县| 珠海市| 南部县| 高陵县| 错那县| 陇川县| 琼结县| 洱源县| 丹巴县| 博白县| 兴山县| 霍邱县| 兴城市| 游戏| 荃湾区| 贺兰县| 喀什市| 洛扎县| 拉萨市| 罗田县| 普陀区| 阿拉尔市| 工布江达县| 黄骅市| 凤冈县| 通城县| 潞城市|