目錄
休眠與讓步" >休眠與讓步
sleep" >sleep
yield" >yield
執行緒優先權" >執行緒優先權
守護/後台(daemon)線程" >守護/後台(daemon)線程
加入執行緒" >加入執行緒
線程組" >線程組
捕获异常" >捕获异常
竞争条件" >竞争条件
同步规则" >同步规则
ReentrantLock" >ReentrantLock
ReentrantLock构造器" >ReentrantLock构造器
锁的获取与释放" >锁的获取与释放
ReentrantLock具有可重入性" >ReentrantLock具有可重入性
条件对象" >条件对象
Condition" >Condition
synchronized" >synchronized
与ReentrantLock比较" >与ReentrantLock比较
静态synchronized" >静态synchronized
內部鎖定和條件的限制" >內部鎖定和條件的限制
首頁 Java java教程 java並發--全部執行緒機制實例詳解

java並發--全部執行緒機制實例詳解

Apr 25, 2017 am 09:20 AM

JAVA多執行緒並不是一個簡單的知識點,而是由很多個瑣碎的內容拼合在一起。有很多我們都說不上來的機制但是很重要,我們這裡就將所有的常用的並發機制全部撈一遍。

休眠與讓步


客觀的影響執行緒任務的簡單方法是呼叫sleep方法,sleep方法中止執行給定的時間,在這段時間過後繼續進行程序中的操作。而與之不同的,我們使用yield方法是在run方法完成一個循環後,yield方法向CPU表示本線程的工作做的差不多了,可以讓其他(具有相同優先權的)線程來使用CPU

sleep

sleep方法會讓你的執行緒固定休眠一段時間,之後再被喚醒繼續執行程式碼。這使得線程調度器可以切換到另一個線程,進而驅動另一個任務。但是,具體驅動的是哪一個任務,這和底層的執行緒機制、作業系統有關。我們不能把程式的安全性寄望於這種執行順序。或者使用同步控制,或根本不適用線程,自己只用協作例程,這些例程才會按照指定的順序在互相之間傳遞控制權。

對sleep的呼叫可能會拋出InterruptedException異常,而這個異常將會在run方法中被捕獲。因為異常不能跨線程傳播回main函數。

yield

我們使用yield方法是在run方法完成一個循環後,yield方法向CPU表示本線程的工作做的差不多了,可以讓其他(具有相同優先權的)執行緒來使用CPU。但要注意的是,yield向CPU的表示並不一定會百分之百採納。事實上,yield經常被誤用。

執行緒優先權


每個執行緒都有一個優先權,我們需要知道的是,在沒經過特殊處理的時候,所有的執行緒優先順序都是一樣的。

預設的,我們把優先權分成1到10之間,高優先權的執行緒會先被操作。說到這裡不由得讓人想起作業系統中進程優先權老化這類名詞。事實上,java虛擬機器也確實是用行程的優先權來類比出執行緒的優先權。但這樣做最大的問題在於,每個作業系統對於進程優先權的處理並不相同,java的執行緒優先權也因此而具有平台變化性

我們可以透過getPriority()方法來取得執行緒的優先權,而且我們也可以隨時使用setPriority()來修改它。

Thread.currentThread().getPriority(); 
  //获取线程优先级 
  Thread.currentThread().setPriority(); 
  //修改线程优先级
登入後複製

我們不應該把程式的正確性依賴於執行緒優先權,我們應該盡量少的使用執行緒優先權。

守護/後台(daemon)線程


daemon thread,我們可以把他翻譯成守護線程或後台線程。

守護線程的作用是為其他的線程提供服務,如果其他所有的線程都被退出,只剩下守護線程,那麼程式也就結束了。沒有去單獨運行守護線程的必要。比如說其他線程的計時器,我們就可以將它設定為一個守護線程。而且守護線程派生出的子線程也是守護線程。

t.setDaemon(ture);  
  //将线程转换为守护线程 
  t.isDaemon();  
  //判断线程是否为守护线程
登入後複製

不要在守護線程中開啟或使用任何資源! 所有非守護執行緒退出時程式終止,那麼你就要想到,後台會在不執行守護執行緒的finally子句的情況下終止其run方法。說出來你可能不信,但確實如此,System.exit(0);是唯一讓finally子句不執行的情況。

加入執行緒


一個執行緒可以在其他執行緒之上呼叫join方法。效果是等待一段時間直到被呼叫的那個執行緒結束之後,再回到這個執行緒繼續往下進行。

首先在這個執行緒中要得到另外一個執行緒的引用,並且使用這個引用,呼叫join()方法。呼叫之後這個執行緒將被掛起,直到目標執行緒結束才恢復。也可以在呼叫join時帶上一個超時參數,這樣如果目標執行緒在這段時間到期時還沒結束的話,join方法也能回傳。

在JAVA SE5中java.util.concurrent類別庫新增加了CyclicBarrier這樣的工具,它們可能比最初的線程類別庫中的join更有效。

線程組


關於線程組,在JDK1.2版本還很流行,但是隨著之後的版本的推出,線程組並不是那麼好用,對於線程組我們可以忽略他。

最好把執行緒組看成一次不成功的嘗試,你只要忽略就好了。 ——Joshua Bloch

捕获异常


因为多线程的机制,我们不能在main函数中捕获其他线程的异常。这就说明,如果我们不能在run方法中自己捕获异常,那么异常会被抛出到控制台上。除非我们采用特殊的机制来捕获。

package AllThread;/**
 * 
 * @author QuinnNorris
 * 
 *         捕获异常
 */public class ExceptionThread {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub

        Thread th = new Thread(new Runnable() {            @Override
            public void run() {                // TODO Auto-generated method stub
                throw new RuntimeException();
            }
        });
        th.start();
    }

}
登入後複製

这是一段简单的代码,它会抛出一个运行时异常:

Exception in thread “Thread-0” java.lang.RuntimeException 
          at AllThread.ExceptionThread$1.run(ExceptionThread.java:15) 
          at java.lang.Thread.run(Thread.java:745)
登入後複製

我们可以看出, 由于没有去设计捕获异常,它被直接输出到控制台上。对于这种情况,为main函数加上try-catch语句是没有用的。

为了解决这种不能捕获未检查异常的情况,在JAVA SE5中引入了使用Executor的一种解决方法。

package AllThread;import java.lang.Thread.UncaughtExceptionHandler;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;/**
 * 
 * @author QuinnNorris
 * 
 *         使用UncaughtExceptionHandler捕获异常
 */public class UEHThread {

    /**
     * @param args
     */
    public static void main(String[] args) {        // TODO Auto-generated method stub
        ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {            @Override
            public Thread newThread(Runnable r) {                // TODO Auto-generated method stub
                Thread th = new Thread(r);
                th.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {                    @Override
                    public void uncaughtException(Thread t, Throwable e) {                        // TODO Auto-generated method stub
                        System.out.println("catch it " + e);
                    }
                });                return th;
            }
        });
        es.execute(new Runnable() {            @Override
            public void run() {                // TODO Auto-generated method stub
                throw new RuntimeException();
            }
        });
    }
}
登入後複製

因为我比较懒全部用内部类来表示,所以这段程序可能略有些难懂。首先我们创建了一个线程池,然后为这个创建线程池的静态方法赋予一个参数。这个参数是一个ThreadFactory类,这个类是用来描述在线程池中的线程具有的共性的。ThreadFactory有一个方法需要我们覆盖就是newThread方法,这个方法的参数是我们要处理的Runnable任务,也就是我们要加入到线程池中的Runnable任务。我们在这个方法中用一个th对象包含r对象,然后设置th对象的UncaughtExceptionHandler属性。这个setUncaughtExceptionHandler方法的参数是一个UncaughtExceptionHandler对象(这里我们第二次用内部类),UncaughtExceptionHandler类的唯一一个方法是uncaughtException。这个方法用来表示对线程未检查异常的处理方式,我们让他在控制台输出一句话。到这里我们对线程池的部署就完成了。

然后我们在这个线程池中添加一个Runnable任务,这个任务会抛出一个未检查异常。现在我们运行程序,控制台输出:

catch it java.lang.RuntimeException

到此,对于线程run方法中的未检查异常的处理就结束了。需要注意的是,我们向线程池中添加线程的方法要调用execute方法而不要使用submit方法,submit方法会把异常吞掉。从而控制台将会什么都不输出。

竞争条件


在操作系统中有一张让人印象深刻的图片。上面画的是一块块并排的进程,在这些进程里面分了几个线程,所有这些线程齐刷刷统一的指向进程的资源。资源会在线程间共享而不是每个线程都有一份独立的资源。在这种共享的情况下,很有可能有多个线程同时在访问一个资源,这种现象我们叫做竞争条件。

在一个银行系统中,每个线程分别管理一个账户,这些线程可能会进行转账的操作。
在一个线程进行操作的时候,他首先,会把账户余额存放到寄存器中,第二步,它将寄存器中的数字减少要转出的钱数,第三步,它将结果写回余额中。
问题在于,这个线程在执行完1、2步时,另外一个线程被唤醒并且修改了第一个线程的账户余额值,但是这个时候第一个线程并不知情。第一个线程等待第二个线程执行完毕后,继续他的第三步:将结果写回余额中。这个时候,它把第二个线程的操作刷掉了,所以钱数发生错误。

同步规则


如果你正在写一个变量,他可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。——Brian

ReentrantLock


上面的例子告诉我们:如果我们的操作不是原子操作,被打断是肯定会发生的。我们没办法把代码变成原子操作,但是能将其上锁来保证安全性。在并发程序中,在访问资源或数据之前,要先给代码套一个锁。在锁被使用的期间,代码中涉及的资源不能被其他的线程访问,直到程序结束时再将锁打开。

ReentrantLock构造器

ReentrantLock类提供了两个构造器:一个是默认构造器,一个是带有公平策略的构造器。

首先,带有公平策略的锁会比正常的锁要慢很多。其次,在某些情况下公平策略并不能保证真正公平的。
如果我们没有特殊的理由真的需要公平策略的时候,尽量不要去使用这种锁。

锁的获取与释放

ReentrantLock myLock = new ReentrantLock();
//创建对象
myLock.lock();
//获取锁try{...}
finally{
myLock.unlock();
//释放锁
}
登入後複製

一定要在finally中释放锁。如果不在finally中释放锁,锁确实将一直得不到释放。正如同我们在调用资源后会使用close()方法。值得一提的,当我们使用锁的时候,我们不能使用try-with-resource,因为这个锁并不是用close来关闭的。

ReentrantLock具有可重入性

如果你要在递归或者循环程序中使用锁,那么就放心的用吧。ReentrantLock锁具有可重入性,他会在每次调用lock()的时候维护一个计数记录着被调用的次数,在每一次的lock调用都必须要用unlock来释放。

条件对象


if(a>b) a.set(b-1);
登入後複製

上面是一个很简单的条件判断,但是我们在并发程序中不能直接这样书写。如果在这个线程刚刚做完判断之后,另外一个线程被唤醒,并且另外一个线程在操作之后使得a小于b(if语句中的条件已经不再正确)。但我们还会执行if中的语句,这是不正确的。

或许会想把整个if语句直接放在锁里面,确保自己的代码不会被打断。但是这样又存在一个问题,如果if判断是false,那么if中的语句不会被执行。但如果我们需要去执行if中的语句,甚至我们要一直等待if判断变的正确之后去执行if中的语句的情况下,这时if语句再也不会变得正确了,因为我们的锁把这个线程锁死,其他的线程没办法访问临界区并修改a和b的值让if判断变得正确。这时候我们只能放弃锁,等待其他线程使用,再获得锁,进行判断,如果判断仍未false就重复之前的操作。这种繁琐的过程是我们不希望的。

通常,线程在上锁进入临界区之后存在一个问题:线程所需的资源,在别的线程中使用或并不满足他们能执行的条件,这个时候我们需要用一个条件对象来管理这些得到了一个锁,但是不能做有用工作的线程

Condition


Condition类在临界区起到了条件对象的作用。

我们用ReentrantLock类中的newCondition方法来获取一个条件对象。

Condition cd = myLock.newCondition();
登入後複製

我们在if语句下面直接跟上await方法,这个方法表示这个线程被阻塞,并放弃了锁,进入等待状态等其他的线程来操作。其他的线程在顺利执行if语句内容之后,调用signalAll方法,这个方法将会重新去激活所有的因为这个条件被阻塞的线程,让这些线程重新获得机会,这些线程被允许从被阻塞的地方继续进行。此时,线程应该再次测试该条件,如果还是不能满足条件,需要再次重复上述操作。

ReentrantLock myLock = new ReentrantLock();//创建锁对象myLock.lock();//给下面的临界区上锁Condition cd = myLock.newCondition();//创建一个Condition对象,这个cd对象表示条件对象while(!(a>b))
    cd.await();//上面的while循环和await方法调用是标准写法//如果不能满足if的条件,那么他将进入阻塞状态,放弃锁,等待别人去激活它a.set(b-1);//一直等到从while循环出来,满足了判断的条件,我们执行自己的功能cd.signalAll();//调用signalAll方法去激活其他的被阻塞的线程。如果所有的线程都在等待其他线程signalAll,则进入死锁
登入後複製

总结来说,Condition对象和锁有这样几个特点。

  1. 锁可以用来保护代码片段,任何时刻只能有一个线程进入被保护的区域

  2. 锁可以管理试图进入临界区的线程

  3. 锁可以拥有一个或多个条件对象

  4. 每个条件对象管理那些因为前面所描述的原因而不能被执行但已经进入被保护代码段的线程

synchronized


ReentrantLock和Condition对象是一种用来保护代码片段的方法。还可以通过使用关键字synchronized来修饰方法,从而给方法添加一个内部锁。java的每一个对象都有一个内部锁,每个内部锁会保护那些被synchronized修饰的方法。也就是说,如果想调用这个方法,首先要获得内部的对象锁。

所有对象都自动含有单一的锁(也叫做监视器)。当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。

与ReentrantLock比较

我们先拿出上面的代码:

public void function(){
    ReentrantLock myLock = new ReentrantLock();
    myLock.lock();

    Condition cd = myLock.newCondition();    while(!(a>b))
        cd.await();

    a.set(b-1);

    cd.signalAll();
}
登入後複製

如果我们用synchronized来实现这段代码,将会变成下面的样子:

public synchronized void function(){
    while(!(a>b))
        wait();

    a.set(b-1);

    notifyAll();
}
登入後複製

需要我们注意的是,在使用synchronized关键词时,无需再去用ReentrantLock和Condition对象,我们用wait方法替换了await方法,notifyAll方法替换了signalAll方法。这样写确实比之前的简单了很多。

静态synchronized

将静态方法声明为synchronized也是合法的。如果调用这种方法,将会获取相关的类对象的内部锁。比如我们调用Test类中的静态方法,这时,Test.class对象的锁将被锁住。

內部鎖定和條件的限制

內部鎖定雖然簡便,但是他存在著許多限制:

  1. 不能中斷一個正在試圖取得鎖定的執行緒

  2. 試圖取得鎖定時不能設定逾時

  3. 因為不能透過Condition來實例化條件。每個鎖僅有單一的條件,可能是不夠的

以上是java並發--全部執行緒機制實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP的影響:網絡開發及以後 PHP的影響:網絡開發及以後 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

PHP與Python:用例和應用程序 PHP與Python:用例和應用程序 Apr 17, 2025 am 12:23 AM

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。

See all articles