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

歡迎訪問 生活随笔!

生活随笔

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

java

java如何写线程外部类_廖雪峰Java读书笔记(六)--多线程(或称并发)

發(fā)布時(shí)間:2024/9/19 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java如何写线程外部类_廖雪峰Java读书笔记(六)--多线程(或称并发) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 多線程基礎(chǔ)

首先要明白一些概念:

進(jìn)程:把一個(gè)任務(wù)稱為一個(gè)進(jìn)程,瀏覽器就是一個(gè)進(jìn)程,視頻播放器是另一個(gè)進(jìn)程,類似的,音樂播放器和Word都是進(jìn)程。

線程:某些進(jìn)程內(nèi)部還需要同時(shí)執(zhí)行多個(gè)子任務(wù)。例如,我們在使用Word時(shí),Word可以讓我們一邊打字,一邊進(jìn)行拼寫檢查,同時(shí)還可以在后臺(tái)進(jìn)行打印,我們把子任務(wù)稱為線程。注:操作系統(tǒng)調(diào)度的最小單位是線程。

進(jìn)程和線程的關(guān)系就是:一個(gè)進(jìn)程可以包含一個(gè)或多個(gè)線程,但至少會(huì)有一個(gè)線程。每個(gè)進(jìn)程都有自己完整的變量,但一個(gè)進(jìn)程內(nèi)的線程共享數(shù)據(jù)。(The essential difference is that while each process has a complete set of its own variables, threads share the same data.)

Java用一個(gè)主線程來執(zhí)行main()方法,在main()方法內(nèi)部我們又可以啟動(dòng)多個(gè)線程。

2. 創(chuàng)建新線程

要?jiǎng)?chuàng)建一個(gè)新線程非常容易,我們需要實(shí)例化一個(gè)Thread實(shí)例,然后調(diào)用它的start()方法即可。

package MultiThr;

public class CreateThread {

public static void main(String[] args) {

Thread t = new Thread();

t.start(); // 啟動(dòng)新線程

}

}

但這個(gè)線程什么都沒有做。如果要?jiǎng)?chuàng)建一個(gè)做事的線程,應(yīng)該這樣做。

方法一:

來自于《Java核心技術(shù)》,其實(shí)是廖雪峰的方法二,但這個(gè)用到了lambda表達(dá)式。

實(shí)現(xiàn)Runnable接口,并實(shí)現(xiàn)其中的run方法。Runnable接口的源代碼如下 :

public interface Runnable{

void run();

}

這是個(gè)函數(shù)式接口,所以可以用lambda表達(dá)式來實(shí)現(xiàn)之:

Runnable r = () -> { /*在此處寫下要執(zhí)行的代碼*/ };

構(gòu)建一個(gè)Thread對(duì)象,并傳入剛才實(shí)現(xiàn)的Runnable接口。

Thread t = new Thread(r);

調(diào)用start()方法:

t.start();

注:在Runnable的實(shí)現(xiàn)中要catch (InterruptedException e)。

// 廖雪峰給出的例子

public class Main{

public static void main(String[] args){

Thread t = new Thread(new MyRunnable());

t.start();

}

class MyRunnable implements Runnable{

@Override

public void run(){

System.out.println("start new thread");

}

}

}

如果用《Java核心技術(shù)》的方法改寫上述代碼,是:

public class Main{

public static void main(String[] args){

Thread t = new Thread(new Runnable() ->{ // 這里用到了lambda表達(dá)式方法

try{

System.out.println("start new thread");

} catch (InterruptedException e){ }

}

);

t.start();

}

}

方法二:

廖雪峰的方法一,《Java核心技術(shù)》中的方法二。

操作:從Thread繼承一個(gè)類出來,并@Override其中的run()方法。

示例:

// 將上面的例子用此方法改寫如下:

public class Main{

public static void main(String[] args){

Thread t = new MyThread();

t.start();

}

}

class MyThread extends Thread{

@Override

public void run(){

/*task codes*/

}

}

小結(jié):

Thread(Runnable target):實(shí)例化新對(duì)象Thread并調(diào)用其中的run()方法。具體如何調(diào)用,看上面的博客文章;

void start():開始線程;

void run():一定要復(fù)寫之。可以開一個(gè)新類extends Thread,也可以實(shí)現(xiàn)public inteface Runnable接口;

static void sleep(long millis):線程睡眠一定時(shí)間

3. 線程狀態(tài)

線程有六態(tài),分別是:

New:新創(chuàng)建的線程,尚未執(zhí)行。

Runnable:運(yùn)行中的線程,正在執(zhí)行run()方法的Java代碼。Runnable的狀態(tài)是可執(zhí)行可不執(zhí)行,只要開始執(zhí)行沒結(jié)束,不管是在執(zhí)行中還是在休息,都是Runnable。

Blocked:運(yùn)行中的線程,因?yàn)槟承┎僮鞅蛔枞鴴炱?#xff1b;

Waiting:運(yùn)行中的線程,因?yàn)槟承┎僮髟诘却小:蜕厦娴腂locked狀態(tài)區(qū)別不大。

Timed Waiting:運(yùn)行中的線程,因?yàn)閳?zhí)行sleep()方法正在計(jì)時(shí)等待;

Terminated:線程已終止,因?yàn)閞un()方法執(zhí)行完畢。

當(dāng)線程啟動(dòng)后,它可以在Runnable、Blocked、Waiting和Timed Waiting這幾個(gè)狀態(tài)之間切換,直到最后變成Terminated狀態(tài),線程終止。

注:New和Terminated是兩頭,其他的是中間。

線程終止的原因有:

線程正常終止:run()方法執(zhí)行到return語句返回;

線程意外終止:run()方法因?yàn)槲床东@的異常導(dǎo)致線程終止;

對(duì)某個(gè)線程的Thread實(shí)例調(diào)用stop()方法強(qiáng)制終止(強(qiáng)烈不推薦使用)。

注:

沒捕獲到的異常也可以終止線程

別用stop()方法!

使用join()方法會(huì)使線程暫停,等其他線程結(jié)束了再來。也就是“讓道”。

4. 線程屬性

廖雪峰部分講到了兩個(gè):

中斷線程(interrupted status)

守護(hù)線程(daemon threads)

在《Java核心技術(shù)》中還有一個(gè),暫時(shí)不知如何翻譯,因?yàn)槲易x的是英文版:

handlers for uncaught exceptions

4.1 中斷線程

有兩種情況會(huì)讓線程中斷:

進(jìn)入return狀態(tài)

沒能捕捉異常

以下方法可以檢查線程是否設(shè)置了中斷狀態(tài),首先我們可以調(diào)用Thread.currentThread()方法來獲取當(dāng)前線程,然后調(diào)用isInterrupted()檢查是否設(shè)置了interrupted()狀態(tài)。但如果線程被鎖定便不能檢查中斷狀態(tài)了。這就是InterruptedException的來源。

在catch到InterruptedException之后,可以檢查一下Thread.currentThread().interrupt();也可以在方法之前就預(yù)先拋出Exception,像這樣:

public void run() throws InterruptedException

4.2 守護(hù)線程

Java程序入口就是由JVM啟動(dòng)main線程,main線程又可以啟動(dòng)其他線程。當(dāng)所有線程都運(yùn)行結(jié)束時(shí),JVM退出,進(jìn)程結(jié)束。如果有一個(gè)線程沒有退出,JVM進(jìn)程就不會(huì)退出。所以,必須保證所有線程都能及時(shí)結(jié)束。

注:也就是說,一切方法都要在main()方法中執(zhí)行。

守護(hù)線程是指為其他線程服務(wù)的線程。在JVM中,所有非守護(hù)線程都執(zhí)行完畢后,無論有沒有守護(hù)線程,虛擬機(jī)都會(huì)自動(dòng)退出。

注:守護(hù)線程除了服務(wù)其他線程以外沒有其他的作用。(《Java核心技術(shù)》)原文:A daemon is simply a thread that has no other role in life than to serve others.

設(shè)置守護(hù)線程的方法:

t.setDaemon(true)

4.3 為線程取名

可以給線程命名:

Thread t = new Thread(runnable);

t.setName("abc"); # 使用setName("name")方法

4.4 解決未捕獲的異常問題

線程也可以被未捕獲的異常終止,然后線程死亡。這時(shí)候就需要處理未捕獲的異常。可以實(shí)現(xiàn)Thread.UncaughtExceptionHandler接口來處理。如下:

void unchangedException(Thread t, Throwable e)

可以以線程中設(shè)置一個(gè)setUncaughtExceptionHandler方法,也可以設(shè)置靜態(tài)方法setDefaultUncaughtExceptionHandler。

ThreadGroup對(duì)象暫時(shí)不是很懂,在《Core Java》P747-748中。就暫時(shí)先翻譯一下:

ThreadGroup類實(shí)現(xiàn)了Thread.UncaughtExceptionHandler接口。其中的uncaughtException方法進(jìn)行以下操作:

如果線程組有父線程,那么會(huì)調(diào)用uncaughtException()方法;

否則,如果Thread.getDefaultUncaughtExceptionHandler方法返回一個(gè)非null的解決方案,便會(huì)調(diào)用uncaughtException方法;

如果Throwable是ThreadDeath的實(shí)例,那么什么也不會(huì)發(fā)生。

線程的名字及Throwable堆棧追蹤會(huì)被輸出 。

5. 線程同步(Synchronization)

前面說過,同一進(jìn)程內(nèi)的多個(gè)線程共享數(shù)據(jù),這樣會(huì)導(dǎo)致競爭情況(race condition)。也就是,當(dāng)多個(gè)線程同時(shí)運(yùn)行時(shí), 線程并不是非常有禮貌地排隊(duì)運(yùn)行,如果不加干預(yù),就是你運(yùn)行一會(huì)它運(yùn)行一會(huì)。如果是兩個(gè)線程同時(shí)運(yùn)行,并不是兩個(gè)各運(yùn)行相等的時(shí)間。要注意。

廖老師的例子:

// https://www.liaoxuefeng.com/wiki/1252599548343744/1306580844806178

public class Main {

public static void main(String[] args) throws Exception {

var add = new AddThread(); // 線程一

var dec = new DecThread(); // 線程二

// 兩個(gè)線程并不是“先來后到”式運(yùn)行的

add.start();

dec.start();

add.join();

dec.join();

System.out.println(Counter.count); // 最后的結(jié)果不一定是0

}

}

class Counter {

public static int count = 0;

}

class AddThread extends Thread {

public void run() {

for (int i=0; i<10000; i++) { Counter.count += 1; }

}

}

class DecThread extends Thread {

public void run() {

for (int i=0; i<10000; i++) { Counter.count -= 1; }

}

}

很可能的運(yùn)行模式如下圖:

從廖老師網(wǎng)站上截圖下來

那么,如果要讓線程之間可以禮讓地運(yùn)行,遵循“先來后到”的順序,怎么辦?就像這樣:

從廖老師網(wǎng)站截圖下來

從圖中可以看出,為保證代碼可以“先來后到”地運(yùn)行,需要通過lock(加鎖)與unlock(解鎖)操作實(shí)現(xiàn)。通過加鎖與解鎖的操作就可以保證一個(gè)線程執(zhí)行期間不會(huì)有其他的線程進(jìn)入此指令區(qū)間。即使在執(zhí)行期線程被操作系統(tǒng)中斷執(zhí)行,其他線程也會(huì)因?yàn)闊o法獲得鎖導(dǎo)致無法進(jìn)入此指令區(qū)間。只有執(zhí)行線程將鎖釋放后,其他線程才有機(jī)會(huì)獲得鎖并執(zhí)行。在專業(yè)術(shù)語中,此操作叫做代碼的原子性(atomic)。

有鎖與無鎖的區(qū)別(圖片來自《Java核心技術(shù)》)

有兩種方法可以實(shí)現(xiàn):ReetrantLock類型與synchronized關(guān)鍵字。《Java核心技術(shù)》先講的是前者,我看書有點(diǎn)懵逼,但廖老師先講的是后者,相對(duì)比較明白一些。

由上面可以得知,保證一段代碼的原子性,可以通過加鎖與解鎖的操作來實(shí)現(xiàn)。不過,在《Java核心技術(shù)》中作者也承認(rèn):

The Lock and Condition interfaces give programmers a high degree of control over locking. However, in most situations, you don't need that control -- you can use a mechanism that is built into the Java language.

其實(shí),“a mechanism that is built into the Java language”就是我們要說的synchronized關(guān)鍵字,在術(shù)語中稱為intrinsicLock。

兩種方法使用:

synchronized(lock)

public synchronized void method()

第一種使用方式表示用lock實(shí)例作為鎖,兩個(gè)線程在執(zhí)行各自的synchronized(Counter.lock) { ... }代碼塊時(shí),必須先獲得鎖,才能進(jìn)入代碼塊進(jìn)行。執(zhí)行結(jié)束后,在synchronized語句塊結(jié)束會(huì)自動(dòng)釋放鎖。第二種方法也不用寫unlock。 但是,它的缺點(diǎn)是帶來了性能下降。因?yàn)閟ynchronized代碼塊無法并發(fā)執(zhí)行。此外,加鎖和解鎖需要消耗一定的時(shí)間,所以,synchronized會(huì)降低程序的執(zhí)行效率。(廖雪峰語)

如何使用synchronized關(guān)鍵字鎖定對(duì)象呢?

找出修改共享變量的線程代碼塊;

選擇一個(gè)共享實(shí)例作為鎖;

使用synchronized(lockObject) { ... }。

注意:

如果要兩個(gè)線程對(duì)同一個(gè)對(duì)象先來后到地操作,那么兩個(gè)線程應(yīng)當(dāng)鎖住同一個(gè)對(duì)象;使用synchronized的時(shí)候,獲取到的是哪個(gè)鎖非常重要。鎖對(duì)象如果不對(duì),代碼邏輯就不對(duì)。

對(duì)幾個(gè)變量要進(jìn)行鎖操作,就設(shè)幾個(gè)鎖。

JVM規(guī)范定義了幾種原子操作。不需要同步:

基本類型(long和double除外)賦值,例如:int n = m;

引用類型賦值,例如:List list = anotherList

如果一個(gè)類被設(shè)計(jì)為允許多線程正確訪問,我們就說這個(gè)類就是“線程安全”的(thread-safe)。Java標(biāo)準(zhǔn)庫的java.lang.StringBuffer也是線程安全的。

如果是第二種方法,可以把整個(gè)方法變?yōu)橥酱a塊,鎖住的對(duì)象是this。

如果鎖住的是static方法,那么鎖住的是class.Class對(duì)象本身。

5.1 死鎖

首先我們要明白,Java線程鎖是可重入的鎖(廖雪峰語)。對(duì)同一個(gè)線程,鎖可以重復(fù)獲得,即JVM允許線程重復(fù)地獲取同一個(gè)鎖,這就是可重入鎖。例如:

public class Counter {

private int count = 0;

public synchronized void add(int n) { // add方法會(huì)獲取一個(gè)鎖

if (n < 0) {

dec(-n); // 同時(shí)dec方法也會(huì)獲得鎖

// JVM同時(shí)允許add()與dec()獲得鎖

} else {

count += n;

}

}

public synchronized void dec(int n) {

count += n;

}

}

那么死鎖是怎么發(fā)生的呢?通俗地說,一個(gè)已經(jīng)獲取鎖的對(duì)象還要再獲取另一個(gè)鎖,但另一個(gè)鎖已經(jīng)被其他對(duì)象把持,死鎖就發(fā)生了。(我自己說的)例如:(廖雪峰老師的例子)

public void add(int m) {

synchronized(lockA) { // 獲得lockA的鎖

this.value += m;

synchronized(lockB) { // 獲得lockB的鎖

this.another += m;

} // 釋放lockB的鎖

} // 釋放lockA的鎖

}

public void dec(int m) {

synchronized(lockB) { // 獲得lockB的鎖

this.another -= m;

synchronized(lockA) { // 獲得lockA的鎖

this.value -= m;

} // 釋放lockA的鎖

} // 釋放lockB的鎖

}

(用廖老師的方法)

分析如上例子:線程1和線程2分別執(zhí)行add()與dec()時(shí):

線程1:進(jìn)入add()方法,獲得lockA;

線程2:進(jìn)入dec()方法,獲得lockB;

然后順序執(zhí)行:

線程1:獲取lockB失敗,因?yàn)橐呀?jīng)被dec()獲取

線程2:獲取lockA失敗,因?yàn)橐呀?jīng)被add()獲取

于是死鎖(Deadlocks)就發(fā)生了。死鎖一旦形成就只能強(qiáng)制結(jié)束進(jìn)程。避免死鎖的方法是嚴(yán)格按照線程獲取鎖的順序來寫!

一旦死鎖發(fā)生,那么可以按Ctrl + \來查看所有線程。每個(gè)線程都有追蹤,告訴你在哪里鎖住了。

5.2 使用wait與notify

Java中synchronized解決了多線程競爭的問題,但并不解決多線程協(xié)調(diào)的問題。先看一個(gè)例子:

package MultiThr;

import java.util.*;

public class TaskQueue {

Queue queue = new LinkedList<>();

public synchronized void addTask(String s){

this.queue.add(s);

}

public synchronized String getTask(){

while (queue.isEmpty()){} // 實(shí)際上while循環(huán)不會(huì)停下來

return queue.remove();

}

}

理論上,如果任務(wù)隊(duì)列為空,就等待,直到線程里有一個(gè)任務(wù)就退出。但事實(shí)上不是這樣的:實(shí)際運(yùn)行中,因?yàn)榫€程在執(zhí)行while()循環(huán)時(shí),已經(jīng)在getTask()入口處獲得了this鎖,導(dǎo)致其他線程無法調(diào)用getTask()方法。最后的結(jié)果是getTask()陷入死循環(huán)。如何修改呢?

package MultiThr;

import java.util.*;

public class TaskQueue {

Queue queue = new LinkedList<>();

public synchronized void addTask(String s){

this.queue.add(s);

}

public synchronized String getTask() throws InterruptedException { // 拋出異常必須

while (queue.isEmpty()){

// 釋放this鎖

this.wait();

}

return queue.remove();

}

}

當(dāng)一個(gè)線程在this.wait()等待時(shí),它就會(huì)釋放this鎖,從而使得其他線程能夠在addTask()方法上獲得this鎖。當(dāng)wait()方法調(diào)用時(shí)會(huì)釋放線程鎖,返回后又重新獲得鎖。

當(dāng)我們用wait()方法讓線程進(jìn)入等待狀態(tài)后,有什么方法可以重新喚起線程嗎?notify()方法,可以喚醒一個(gè)正在this鎖等待的線程。方法如下:

public synchronized void addTask(String s) {

this.queue.add(s);

this.notify(); // 喚醒在this鎖等待的線程

}

5.3 再談ReentrantLock

由于《Java核心技術(shù)》一上來就講到了ReentrantLock,我有點(diǎn)不懂。現(xiàn)在學(xué)完之后再回來看ReentrantLock,似乎可以懂了。

首先,ReentrantLock在java.uitl.concurrent.locks,屬于并發(fā)編程。

上面的Counter類例子:

public class Counter {

private int count;

public void add(int n) {

synchronized(this) {

count += n;

}

}

}

用ReentrantLock改造一下是(廖老師例子):

import java.util.concurrent.locks.*;

public class Counter{

private int count;

private final Lock lock = new ReentrantLock(); // 在方法外圍定義一個(gè)鎖

public void add(int n){

lock.lock(); // 方法最一開始就加鎖

try{ // 要用try語句

count += n;

} finally{ lock.unlock(); } // 一定要釋放鎖

}

}

注1:(《Java核心技術(shù)》語) It is critically important that the unlock operation is enclosed in a finally clause. (譯:在finally語句中使用unlock語句釋放鎖)

注2:(《Java核心技術(shù)》語) You cannot use the try-with-resources statement. (譯:在ReentrantLock中,不能使用try帶括號(hào))

ReentrantLock也是可重入鎖,也就是說一個(gè)線程可以多次獲取同一個(gè)鎖,得鎖者運(yùn)行。

前面已經(jīng)說到用wait()方法和notifyAll()方法實(shí)現(xiàn)多線程協(xié)調(diào)。那么在ReentrantLock中有何方法?Condition類中提供的await()、signal()、signalAll()可以實(shí)現(xiàn)同樣的功能。

手?jǐn)]一遍廖老師的代碼:

import java.util.concurrent.locks.*;

public class TaskQueue{

private final Lock lock = new ReentrantLock();

private final Condition condition = lock.newCondition();

private Queue queue = new LinkedList<>();

public void addTask(String S){

lock.lock();

try{

queue.add(s);

condition.signalAll(); // notifyAll()

}finally{

lock.unlock();

}

}

public void getTask(){

lock.lock();

try{

while (queue.isEmpty()){

condition.await();

}finally{ lock.unlock(); }

return queue.remove();

}

}

}

5.4 讀寫鎖

不論是synchronized()方法也好,還是ReentrantLock也罷,都只能允許一個(gè)線程進(jìn)行讀寫,如果不寫入的話,其他線程讀取都困難,因?yàn)闆]有獲取鎖。但我們想要的效果是允許多個(gè)線程同時(shí)讀,但只要有一個(gè)線程在寫,其他線程就必須等待。換句話說,如果沒人寫,其他的都可以讀;如果寫了,其他的就不可讀了。這個(gè)問題可以用ReadWriteLock解決。

使用ReadWriteLock時(shí),適用條件是同一個(gè)數(shù)據(jù),有大量線程讀取,但僅有少數(shù)線程修改。比如論壇的帖子。

但它有一個(gè)問題:既某個(gè)線程要寫的時(shí)候,需要釋放讀鎖才能寫。于是悲觀鎖發(fā)生了。

示例:

package MultiThr;

import java.util.concurrent.locks.*;

import java.util.*;

public class CounterRW {

private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

private final Lock rLock = rwLock.readLock();

private final Lock wLock = rwLock.writeLock();

private int[] counts = new int[10];

// 把讀寫操作分別用讀鎖和寫鎖來加鎖,在讀取時(shí),多個(gè)線程可以同時(shí)獲得讀鎖,這樣就大大提高了并發(fā)讀的執(zhí)行效率。

public void inc(int index){

wLock.lock(); // 寫鎖

try{

counts[index] += 1;

}finally{

wLock.unlock();

}

}

public int[] get(){

rLock.lock(); // 讀鎖

try{

return Arrays.copyOf(counts, counts.length);

}finally{ rLock.unlock(); }

}

}

5.5 悲觀鎖與樂觀鎖

先來看廖雪峰老師的簡版定義:

悲觀鎖:如果有線程正在讀,寫線程需要等待讀線程釋放鎖后才能獲取寫鎖,即讀的過程中不允許寫,這是一種悲觀的讀鎖。

樂觀鎖:讀的過程中大概率不會(huì)有寫入,因此被稱為樂觀鎖。

再來看看技術(shù)博客中如何定義:

悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。synchronized與ReentrantLock還有ReadWriteLock都屬于悲觀鎖范圍。相對(duì)更安全,但效率不高。

樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。

5.6 StampedLock

這是Java 8開始引進(jìn)的一種讀寫鎖,讀的過程也允許獲取寫鎖后寫入。但需要一些代碼判斷是否有寫入。例:

package MultiThr;

import java.util.*;

import java.util.concurrent.locks.*;

public class Point {

public final StampedLock stampedLock = new StampedLock();

private double x;

private double y;

public void move(double deltaX, double deltaY){

long stamp = stampedLock.writeLock(); // 獲取寫鎖

try{

x += deltaX;

y += deltaY;

}finally{

stampedLock.unlockWrite(stamp); // 釋放寫鎖

}

}

public double distanceFromOrigin(){

long stamp = stampedLock.tryOptimisticRead();

double currentX = x;

double currentY = y;

if (!stampedLock.validate(stamp)){ // 檢查是否有其他寫鎖發(fā)生

stampedLock.readLock(); // 這是個(gè)悲觀鎖

try{

currentX = x;

currentY = y;

} finally{

stampedLock.unlockRead(stamp);

}

}

return Math.sqrt(currentX * currentX + currentY + currentY);

}

}

5.7 Concurrent集合與Atomic操作以及volatile關(guān)鍵字

首先應(yīng)明白何為“線程安全”。

線程安全就是多線程訪問時(shí),采用了加鎖機(jī)制,當(dāng)一個(gè)線程訪問該類的某個(gè)數(shù)據(jù)時(shí),進(jìn)行保護(hù),其他線程不能進(jìn)行訪問直到該線程讀取完,其他線程才可使用。

引用地址

Concurrent集合:由于默認(rèn)類并非線程安全,所以調(diào)用時(shí)為了實(shí)現(xiàn)線程安全可以加鎖。但Java的并發(fā)機(jī)制已經(jīng)為我們寫好了,即Concurrent集合,線程安全類。對(duì)照表如下:

interface

non-thread-safe

thread-safe

List

ArrayList

CopyOnWriteArrayList

Map

HashMap

ConcurrentHashMap

Set

HashSet / TreeSet

CopyOnWriteArraySet

Queue

ArrayDeque / LinkedList

ArrayBlockingQueue / LinkedBlockingQueue

Deque

ArrayDeque / LinkedList

LinkedBlockingQueue

volatile關(guān)鍵字:不加鎖也可以實(shí)現(xiàn)同步的一種機(jī)制,使得field可以被同其他線程同步。

例:

private volatile boolean done;

public boolean isDone(){ return done; }

public void setDone(){ done = true; }

6. 線程池

定義:能接受大量小任務(wù)并進(jìn)行分發(fā)處理,使用ExecutorSerivce接口來表示。

ExecutorService有三個(gè)常用實(shí)現(xiàn):

FixedThreadPool:固定大小的線程池

CachedThreadPool:根據(jù)任務(wù)動(dòng)態(tài)調(diào)整的線程池

SingleThreadExecutor:僅單線執(zhí)行的線程池

ScheduledThreadPool:定期反復(fù)執(zhí)行的線程池

示例:

package MultiThr;

import java.util.concurrent.*;

public class ThrPool {

// 創(chuàng)建一個(gè)固定大小的線程池

ExecutorService es = Executors.newFixedThreadPool(4);

for(int i = 0; i <= 5; i++){

es.submit(new Task("" + i));

}

// 關(guān)閉線程池

es.shutdown(); // 使用shutdown關(guān)閉線程池的時(shí)候,線程池會(huì)等待當(dāng)前任務(wù)完成

// shutdownNow(): 立即停止當(dāng)前正在執(zhí)行的任務(wù)

// awaitTermination()則會(huì)等待指定的時(shí)間讓線程池關(guān)閉

}

class Task implements Runnable{

private final String name;

public Task(String name){

this.name = name;

}

@Override

public void run(){

System.out.println("start task" + name);

try{

Thread.sleep(1000);

}catch(InterruptedException e){}

System.out.println("end task" + name);

}

}

6.1 動(dòng)態(tài)調(diào)整的線程池

在簡介中說過CachedThreadPool可以實(shí)現(xiàn)這一功能。扒一下CachedThreadPool的源碼:

public static ExecutorService newCachedThreadPool() {

return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

60L, TimeUnit.SECONDS,

new SynchronousQueue());

}

所以可以這樣寫:

int min = 4 ;

int max = 10;

ExecutorService es = new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new SynchronousQueue());

6.2 定期反復(fù)執(zhí)行的線程池

例如:每秒刷新證券價(jià)格的任務(wù)就可以通過這種線程池來執(zhí)行。依然要通過Executors類來創(chuàng)建。

ScheduledExecutorService ses = Executors.newScheduledThreadPool(4);

一次性任務(wù):

ses.schedule(new Task("one-time"), 1, TimeUnit.SECONDS);

以固定每3秒執(zhí)行一次。FixedRate是指任務(wù)總是以固定時(shí)間間隔觸發(fā),不管任務(wù)執(zhí)行多長時(shí)間:

// 2秒后開始執(zhí)行定時(shí)任務(wù),每3秒執(zhí)行一次

ses.scheduleAtFixedRate(new Task("fixed-rate"), 2, 3, TimeUnit.SECONDS);

每次任務(wù)執(zhí)行間隔3秒。FixedDelay是指,上一次任務(wù)執(zhí)行完畢后,等待固定的時(shí)間間隔,再執(zhí)行下一次任務(wù):

// 2秒后開始執(zhí)行定時(shí)任務(wù),每個(gè)任務(wù)之間間隔3秒

ses.scheduleWithFixedDelay(new Task("fixed-delay"), 2, 3, TimeUnit.SECONDS);

7. Runnable接口

在前作說過創(chuàng)建新任務(wù)可以實(shí)現(xiàn)Runnable接口。但這個(gè)接口有一個(gè)問題:是一個(gè)void方法,并無返回值,所以執(zhí)行一些有返回值的任務(wù)時(shí)候就有所不便。

解決這個(gè)問題的方法是使用Callable接口。與Runnable相比多一個(gè)返回值。示例:

class Task implements Callable{

public String call() throws Exception{

return longTimeCalculation();

}

}

從以上代碼可以看出,Callable是一個(gè)泛型接口,<>當(dāng)中標(biāo)注要返回的類型,實(shí)現(xiàn)call方法可以返回指定的結(jié)果。

8. Future類型

一個(gè)Future類型的實(shí)例代表一個(gè)未來能獲取結(jié)果的對(duì)象,可以獲取異步執(zhí)行的結(jié)果。ExecutorService.submit()方法可以返回一個(gè)Future()類型。

ExecutorService es = Executors.newFixedThreadPool(4);

// 定義任務(wù)

Callable task = new Task();

// 提交任務(wù)并獲得Future

Future f = es.submit(task);

// 從Future返回異步執(zhí)行的結(jié)果

String s = f.get();

Future的方法有:

get() :獲取結(jié)果

get(long timeout, TimeUnit unit):獲取結(jié)果,但只等待指定的時(shí)間

cancel(boolean mayInterruptIfRunning): 取消當(dāng)前任務(wù)

isDone():判斷任務(wù)是否完成

9. Fork/Join

其思想為x分法:如果一個(gè)任務(wù)比較大,那就把它分為x部分執(zhí)行。

示例代碼(先粘貼,回去打):

public class Main {

public static void main(String[] args) throws Exception {

// 創(chuàng)建2000個(gè)隨機(jī)數(shù)組成的數(shù)組:

long[] array = new long[2000];

long expectedSum = 0;

for (int i = 0; i < array.length; i++) {

array[i] = random();

expectedSum += array[i];

}

System.out.println("Expected sum: " + expectedSum);

// fork/join:

ForkJoinTask task = new SumTask(array, 0, array.length);

long startTime = System.currentTimeMillis();

Long result = ForkJoinPool.commonPool().invoke(task);

long endTime = System.currentTimeMillis();

System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");

}

static Random random = new Random(0);

static long random() {

return random.nextInt(10000);

}

}

class SumTask extends RecursiveTask {

static final int THRESHOLD = 500;

long[] array;

int start;

int end;

SumTask(long[] array, int start, int end) {

this.array = array;

this.start = start;

this.end = end;

}

@Override

protected Long compute() {

if (end - start <= THRESHOLD) {

// 如果任務(wù)足夠小,直接計(jì)算:

long sum = 0;

for (int i = start; i < end; i++) {

sum += this.array[i];

// 故意放慢計(jì)算速度:

try {

Thread.sleep(1);

} catch (InterruptedException e) {

}

}

return sum;

}

// 任務(wù)太大,一分為二:

int middle = (end + start) / 2;

System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));

SumTask subtask1 = new SumTask(this.array, start, middle);

SumTask subtask2 = new SumTask(this.array, middle, end);

invokeAll(subtask1, subtask2);

Long subresult1 = subtask1.join();

Long subresult2 = subtask2.join();

Long result = subresult1 + subresult2;

System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);

return result;

}

}

10. ThreadLocal

在同一線程中傳遞同一對(duì)象。回去慢慢更。

與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖

總結(jié)

以上是生活随笔為你收集整理的java如何写线程外部类_廖雪峰Java读书笔记(六)--多线程(或称并发)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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

主站蜘蛛池模板: 亚洲区 欧美区 | 成人在线观看免费高清 | 肉肉h | 69视频免费在线观看 | 国产femdom调教7777 | 欧美久久久久久 | 凹凸精品一区二区三区 | 亚洲美女一级片 | 男人的亚洲天堂 | 欧美中文网 | 黄页视频在线观看 | 亚洲一卡一卡 | 日本妇女毛茸茸 | 国产欧美综合视频 | 999热精品 | 亚洲av无码一区二区三区网站 | 天天色影 | 精品国产一区二区三区日日嗨 | 在线观看一区 | 一级影片在线观看 | av超碰| 特大黑人娇小亚洲女mp4 | 图片一区二区 | 少妇毛片一区二区三区粉嫩av | 国产精品乱轮 | 国产有码在线 | jlzzjlzzjlzz亚洲人| 久99热| 久久久久人妻一区二区三区 | 国产,日韩,欧美 | 国产精品久久久久久久 | 91成年版 | 国产又粗又长又硬免费视频 | 91一区在线观看 | 九九热视频精品 | 欧美精品99久久 | 黄色永久免费网站 | 男人天堂2014| 色综合成人 | 黄黄的视频在线观看 | 久久青青草原 | av天天有| 国产美女又黄又爽又色视频免费 | www.色呦呦 | 日韩视频一区二区三区 | 国产精品久久久久久精 | 久久99国产视频 | 一区二区不卡 | 亚洲精品v天堂中文字幕 | www.色在线观看 | 一区二区美女 | av免费在线观看不卡 | 女人18毛片毛片毛片毛片区二 | 香蕉久久一区二区三区 | 精品久久久久久久中文字幕 | 小草av| 欧美在线视频第一页 | 中文字幕25页 | 免费美女av | 国产一区二区在线播放视频 | 成人午夜在线免费观看 | 久久精品一二三 | 日韩欧美网站 | 国产成人免费 | 亚洲国产高清国产精品 | 熟妇人妻中文av无码 | 欧美黑人一级爽快片淫片高清 | 日本天堂网在线观看 | 久热精品视频在线 | 成人激情免费视频 | 91爱视频 | 一级少妇毛片 | 麻豆av网址 | 欧美国产日韩在线 | 日韩18p| 免费在线黄| 中国无码人妻丰满熟妇啪啪软件 | 好姑娘在线观看高清完整版电影 | 国产日本精品视频 | av网页在线| 日韩欧美中文在线 | 夜色资源网| 久久亚洲av永久无码精品 | 久操视频免费观看 | 精品无码一区二区三区 | 日本二区在线观看 | 涩视频在线观看 | 四虎国产成人精品免费一女五男 | 男女一区| 黄页在线观看 | 中文字幕一区二区三区乱码不卡 | 午夜性色福利影院 | 国产网红主播精品av | 国产一区二区三区精品在线 | 激情丁香婷婷 | 国产91丝袜在线播放九色 | 69社| 国产av电影一区二区三区 | 亚洲精品成人片在线观看精品字幕 |