日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

《Java 高并发》04 线程的基本操作

發(fā)布時(shí)間:2023/12/10 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Java 高并发》04 线程的基本操作 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

新建線程

新建線程很簡(jiǎn)單。只要使用new 關(guān)鍵字創(chuàng)建一個(gè)線程對(duì)象,并且調(diào)用 start 方法啟動(dòng)線程。

Thread t = new Thread(); t.start();

注意:run 方法不是用來啟動(dòng)線程。如果調(diào)用 run 方法它只會(huì)作為普通方法來執(zhí)行,而不會(huì)開啟線程執(zhí)行。

終止線程

一般來說,線程在執(zhí)行完畢后就會(huì)結(jié)束,無須手工關(guān)閉。但凡是都有例外。Thread 類提供了一個(gè) stop 方法來終止線程。如果調(diào)用 stop 方法,就可以立即將一個(gè)線程終止。

目前 stop 方法已經(jīng)過期。因?yàn)?stop 方法太過于暴力,它會(huì)把執(zhí)行到一半的線程終止,此時(shí)可能會(huì)引起數(shù)據(jù)不一致問題。

舉例:對(duì)象 User 有 id、name 兩個(gè)屬性。寫線程總是把 id、name 寫成相同的值。當(dāng)寫線程在寫對(duì)象時(shí),讀線程由于無法獲得鎖,因此必須等待,所以讀線程是看不見一個(gè)寫了一半的對(duì)象。此時(shí),寫線程寫完id后,很不辛被 stop,此時(shí)對(duì)象 u 的 id 為1,而 name 任然為0,出于不一致狀態(tài)。而被終止的寫線程簡(jiǎn)單地講鎖釋放,度線程獲取到鎖后,讀取數(shù)據(jù),于是讀到了 id=1 而 name=0 。

public class StopThreadTest {public static User u = new User();public static class User {private int id;private String name;public User() {this.id = 0;this.name = "0";}public User(int id, String name) {this.id = id;this.name = name;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public int getId() {return id;}public String getName() {return name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}public static class ChangeObjectThread extends Thread {@Overridepublic void run() {while (true) {synchronized (u) {int i = (int) (System.currentTimeMillis() / 1000);u.setId(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}u.setName(String.valueOf(i));}}}}public static class ReadObjectThread extends Thread {@Overridepublic void run() {while (true) {synchronized (u) {if (u.getId() != Integer.valueOf(u.getName())) {System.out.println(u.toString());}}}}}public static void main(String[] args) {try {new ReadObjectThread().start();while (true) {ChangeObjectThread thread = new ChangeObjectThread();thread.start();Thread.sleep(150);thread.stop();}} catch (InterruptedException e) {e.printStackTrace();}} }

打印結(jié)果:

User{id=1619771639, name='1619771638'} User{id=1619771640, name='1619771639'}

那么如果優(yōu)雅的停止一個(gè)線程,又不會(huì)產(chǎn)生數(shù)據(jù)不一致問題?可以考慮定義一個(gè)開關(guān),通過開關(guān)去控制。

public class StopThreadTest {public static User u = new User();public static boolean stopme = true;public static class User {private int id;private String name;public User() {this.id = 0;this.name = "0";}public User(int id, String name) {this.id = id;this.name = name;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public int getId() {return id;}public String getName() {return name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}public static void stopMe(){stopme = false;}public static class ChangeObjectThread extends Thread {@Overridepublic void run() {while (stopme) {synchronized (u) {int i = (int) (System.currentTimeMillis() / 1000);u.setId(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}u.setName(String.valueOf(i));}}}}public static class ReadObjectThread extends Thread {@Overridepublic void run() {while (true) {synchronized (u) {if (u.getId() == Integer.valueOf(u.getName())) {System.out.println(u.toString());}System.out.println(u.toString());}}}}public static void main(String[] args) {try {new ReadObjectThread().start();while (true) {ChangeObjectThread thread = new ChangeObjectThread();thread.start();Thread.sleep(150);stopMe();}} catch (InterruptedException e) {e.printStackTrace();}} }

日志打印:

User{id=1619774686, name='1619774686'} User{id=1619774686, name='1619774686'} User{id=1619774686, name='1619774686'}

線程中斷

從表面上理解,中斷就是讓目標(biāo)線程停止執(zhí)行的意思,實(shí)際上并非如此。

嚴(yán)格來講,線程中斷并不會(huì)是線程立即退出,而是給線程發(fā)送一個(gè)通知,告知目標(biāo)線程,有人希望你退出。至于目標(biāo)線程是否退出,由目標(biāo)線程自己決定。

線程中斷三個(gè)方法:

// 中斷線程 public void interrupt(); // 判斷線程是否中斷 public boolean isInterrupted(); // 判斷線程是否中斷,并清楚當(dāng)前中斷狀態(tài) public static boolean interrupted();

interrupt() 方法通知目標(biāo)方法中斷,也就是設(shè)置中斷標(biāo)志位,中斷標(biāo)志位表示當(dāng)前線程已經(jīng)被中斷了;isInterrupted() 判斷當(dāng)前線程是否有被中斷;interrupted() 也是用來判斷當(dāng)前線程是否被中斷,但同時(shí)會(huì)清除當(dāng)前線程的中斷標(biāo)志位狀態(tài)。

public void interruptTest1(){try {Thread t = new Thread() {@Overridepublic void run() {while (true) {Thread.yield();}}};t.start();Thread.sleep(2000);t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}

線程t 雖然進(jìn)行了中斷,但是并沒有線程中斷后處理的邏輯,因此線程t 即使被中斷,但是這個(gè)中斷不會(huì)發(fā)生任何左右。

優(yōu)化:線程中斷就退出while

public void interruptTest2() {try {Thread t = new Thread() {@Overridepublic void run() {while (true) {// 判斷當(dāng)前線程是否被中斷 if (Thread.currentThread().isInterrupted()){System.out.println("Interrupted");break;}Thread.yield();}}};t.start();Thread.sleep(2000);t.interrupt();} catch (InterruptedException e) {e.printStackTrace();}}

等待和通知

為了支持多線程之間的協(xié)作,JDK 提供了兩個(gè)非常重要的接口線程等待 wait() 和通知 notify()。注意,這兩個(gè)方法不是在 Thread 類中,而是在 Object 類。這也意味著任何對(duì)象都能調(diào)用。

public final void wait() throws InterruptedException; public final native void notify();

當(dāng)一個(gè)對(duì)象實(shí)例調(diào)用wait 方法后,當(dāng)前線程就會(huì)在這個(gè)對(duì)象上等待。比如,線程A 中,調(diào)用了obj.wait() 方法,那么線程A 就會(huì)停止繼續(xù)執(zhí)行,轉(zhuǎn)為等待狀態(tài)。當(dāng)其他線程調(diào)用obj.notify() 方法為止結(jié)束等待狀態(tài)。此時(shí)obj 對(duì)象就儼然成為多個(gè)線程之間的有效通訊手段。

擴(kuò)展

面試題:多線程之間的通訊方式?

  • wait()、notify()
  • 同步 synchronized
  • while 輪訓(xùn)
  • 管道通信(PipedInputStream、PipedOutPutStream)
  • PS:清楚有這么一個(gè)東西即可,如何實(shí)現(xiàn)水平有限,可自行查閱。有錯(cuò)請(qǐng)指教

    wait()、notify() 工作過程:如果一個(gè)線程調(diào)用了 object.wait() 方法,那么它就會(huì)進(jìn)入object 對(duì)象的等待隊(duì)列。在這個(gè)隊(duì)列中,可能會(huì)有多個(gè)線程。當(dāng)調(diào)用 object.notify() 被調(diào)用時(shí),它會(huì)從這個(gè)等待隊(duì)列中,隨機(jī)選擇一個(gè)線程,并將它喚醒。同時(shí) Object 對(duì)象還提供了另一個(gè)方法 notifyAll() 方法,它和notify() 功能基本一致,不同的是notifyAll 會(huì)喚醒這個(gè)隊(duì)列中的所有等待的線程,而不是隨機(jī)選擇一個(gè)。

    強(qiáng)調(diào),調(diào)用wait() 方法必須在 snchronzied 語句中,無論是wait()、notify() 都需要先獲得鎖,當(dāng)執(zhí)行wait() 方法后,會(huì)釋放這個(gè)鎖。這樣做的目的是使得其他等待該鎖的線程不至于無法正常執(zhí)行。

    public class WaitNotifyTest {final static Object object = new Object();public static class T1 extends Thread {@Overridepublic void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ": T1 start");try {System.out.println(System.currentTimeMillis() + ": T1 wait for object");object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(System.currentTimeMillis() + ": T1 end");}}}public static class T2 extends Thread {@Overridepublic void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ": T2 start ! notify one thread");object.notify();System.out.println(System.currentTimeMillis() + ": T2 end");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {T1 t1 = new T1();T2 t2 = new T2();t1.start();t2.start();}}

    如上,兩個(gè)線程 t1、t2。t1 執(zhí)行 object.wait() 方法前,獲取object 對(duì)象鎖。因此,在執(zhí)行 object.wait() 是,它是持有 object 鎖,wait() 執(zhí)行后,t1 會(huì)進(jìn)入等待,并釋放 object 的鎖。t2 在執(zhí)行 notify() 之前也會(huì)先獲取 object 的對(duì)象鎖。t1 在得到 notify() 通知后,還是會(huì)先嘗試重新獲取 object 鎖。上述運(yùn)行日志打印:

    1620273470618: T1 start 1620273470618: T1 wait for object 1620273470618: T2 start ! notify one thread 1620273470618: T2 end 1620273472620: T1 end

    掛起和繼續(xù)執(zhí)行

    掛起suspend 和繼續(xù)執(zhí)行resume 是一對(duì)相反的操作,被掛起suspend 的線程,必須要等到繼續(xù)執(zhí)行resume 操作后,才能繼續(xù)執(zhí)行。目前 suspend()、resume() 已經(jīng)過時(shí),不推薦使用。

    使用 suspend() 掛起線程會(huì)導(dǎo)致線程被暫停,同時(shí)并不會(huì)釋放任何鎖資源。此時(shí),其他線程想要訪問被它暫用的鎖時(shí),都會(huì)導(dǎo)致無法正常繼續(xù)執(zhí)行。直到對(duì)應(yīng)的線程進(jìn)行了resume() 操作,被掛起的線程才能繼續(xù),從而其他阻塞的線程才可以繼續(xù)執(zhí)行。嚴(yán)重的情況是:它暫用的鎖不會(huì)被釋放,因此可能會(huì)導(dǎo)致整個(gè)系統(tǒng)工作不正常。而且,對(duì)于被掛起的線程,從它的線程狀態(tài)上看,居然還是Runnable ,嚴(yán)重影響對(duì)系統(tǒng)當(dāng)前狀態(tài)的判斷。

    public class SuspengResumeTest {public static Object object = new Object();static ChangeObjectThread t1 = new ChangeObjectThread("t1");static ChangeObjectThread t2 = new ChangeObjectThread("t2");public static class ChangeObjectThread extends Thread{public ChangeObjectThread(String name) {super.setName(name);}@Overridepublic void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + " in "+ getName());Thread.currentThread().suspend();System.out.println(System.currentTimeMillis() + " in "+ getName());}}}public static void main(String[] args) {try {t1.start();Thread.sleep(1000);t2.start();t1.resume();t2.resume();t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}} }

    結(jié)果打印:

    1620285481858 in t1 1620285482859 in t1 1620285482859 in t2

    通過日志發(fā)現(xiàn),他們都獲取到了鎖。但是線程不會(huì)退出,而是是會(huì)掛起。雖然主函數(shù)已經(jīng)調(diào)用了 resume() ,但是由于事件先后順序的緣故,導(dǎo)致 t2 線程被永遠(yuǎn)掛起,并且占用了對(duì)象鎖。

    優(yōu)化 suspend()、resume():

    public class SuspengResumeTest2 {public static Object object = new Object();public static class ChangeObjectThread extends Thread {volatile boolean suspendme = false;public void suspendsMe() {suspendme = true;}public void resumeMe() {suspendme = false;synchronized (this) {notify();}}@Overridepublic void run() {while (true) {synchronized (this) {while (suspendme) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}synchronized (object) {System.out.println("in ChangeObjectThread");}Thread.yield();}}}public static class ReadObjectThread extends Thread{@Overridepublic void run() {while (true) {synchronized (object) {System.out.println("in ReadObjectThread");}Thread.yield();}}}public static void main(String[] args) {try {ChangeObjectThread t1 = new ChangeObjectThread();ReadObjectThread t2 = new ReadObjectThread();t1.start();t2.start();Thread.sleep(1000);t1.suspendsMe();System.out.println("suspend t1 2 sec");Thread.sleep(2000);System.out.println("resume t1");t1.resumeMe();} catch (InterruptedException e) {e.printStackTrace();}}}

    等待線程結(jié)束join 和謙讓yield

    很多時(shí)候,一個(gè)線程的執(zhí)行很可能需要依賴于另外一個(gè)或者多個(gè)線程執(zhí)行完畢之后才能繼續(xù)執(zhí)行。比如,日常工作需要產(chǎn)品先出需求文檔,然后召開需求評(píng)審,緊接著進(jìn)行軟件開發(fā)。JDK 提供了 join() 來實(shí)現(xiàn)這個(gè)功能。

    public final void join() throws InterruptedException; public final synchronized void join(long millis) throws InterruptedException;

    第一個(gè) join() 表示無限等待,他會(huì)一致阻塞當(dāng)前線程,直到目標(biāo)線程執(zhí)行完畢。

    第二個(gè) join(long) 表示最大等待時(shí)間,如果超過給定時(shí)間目標(biāo)線程還在執(zhí)行,當(dāng)前線程也會(huì)因?yàn)椤暗炔患傲恕?#xff0c;而繼續(xù)往下執(zhí)行。

    public class JoinTest {public volatile static int num = 1;public static class JoinThread extends Thread {@Overridepublic void run() {for (; num < 100000000; num++) ;}}public static void main(String[] args) {try {JoinThread joinThread = new JoinThread();joinThread.start();joinThread.join();System.out.println("num :" + num);} catch (InterruptedException e) {e.printStackTrace();}}}

    結(jié)果打印:

    num :100000000

    如果把 joinThread.join(); 注釋掉,查看日志 num :1 。

    主函數(shù)在等待 joinThread 線程執(zhí)行完畢再繼續(xù)執(zhí)行,此時(shí) num 為 100000000。

    擴(kuò)展

    join() 的本質(zhì)是讓調(diào)用線程 wait() 在當(dāng)前線程對(duì)象實(shí)例上。源碼:

    public final void join() throws InterruptedException {join(0); } public final synchronized void join(long millis) throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

    可以看到,它讓調(diào)用線程在當(dāng)前線程對(duì)象上進(jìn)行等待。當(dāng)線程執(zhí)行完成后,被等待的線程會(huì)在退出前調(diào)用 notifyAll() 通知所有的等待線程繼續(xù)執(zhí)行。因此,不建議直接在 Thread 對(duì)象實(shí)例上使用類似于 wait()和notify() 等方法,因?yàn)檫@有可能影響系統(tǒng)API的工作。

    Thread 類中的另一個(gè)方法 yield(),定義:

    public static native void yield();

    靜態(tài)方法,一大執(zhí)行,它會(huì)使得當(dāng)前線程讓出CPU。但是要注意,讓出CPU 并不表示當(dāng)前線程不執(zhí)行。當(dāng)前線程在讓出CPU 后,還會(huì)進(jìn)行CPU 資源的爭(zhēng)奪,能夠再次被分配就不一定了。因此,Thread.yield() 的調(diào)用就好像再說,我已經(jīng)完成了一些最重要的工作了,可以休息一下了,可以給其他線程一些工作機(jī)會(huì)!

    總結(jié)

    以上是生活随笔為你收集整理的《Java 高并发》04 线程的基本操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。