java 访问线程_java线程简介(共享对数据的访问)
1、 共享變量
要使多個(gè)線程在一個(gè)程序中有用,它們必須有某種方法可以互相通信或共享它們的結(jié)果。
讓線程共享其結(jié)果的最簡單方法是使用共享變量。它們還應(yīng)該使用同步來確保值從一個(gè)線程正確傳播到另一個(gè)線程,以及防止當(dāng)一個(gè)線程正在更新一些相關(guān)數(shù)據(jù)項(xiàng)時(shí),另一個(gè)線程看到不一致的中間結(jié)果。
線程基礎(chǔ)中計(jì)算素?cái)?shù)的示例使用了一個(gè)共享布爾變量,用于表示指定的時(shí)間段已經(jīng)過去了。這說明了在線程間共享數(shù)據(jù)最簡單的形式是:輪詢共享變量以查看另一個(gè)線程是否已經(jīng)完成執(zhí)行某項(xiàng)任務(wù)。
2、存在于同一個(gè)內(nèi)存空間中的所有線程
正如前面討論過的,線程與進(jìn)程有許多共同點(diǎn),不同的是線程與同一進(jìn)程中的其它線程共享相同的進(jìn)程上下文,包括內(nèi)存。這非常便利,但也有重大責(zé)任。只要訪問共享變量(靜態(tài)或?qū)嵗侄?,線程就可以方便地互相交換數(shù)據(jù),但線程還必須確保它們以受控的方式訪問共享變量,以免它們互相干擾對方的更改。
任何線程可以訪問所有其作用域內(nèi)的變量,就象主線程可以訪問該變量一樣。素?cái)?shù)示例使用了一個(gè)公用實(shí)例字段,叫做 finished,用于表示已經(jīng)過了指定的時(shí)間。當(dāng)計(jì)時(shí)器過期時(shí),一個(gè)線程會寫這個(gè)字段;另一個(gè)線程會定期讀取這個(gè)字段,以檢查它是否應(yīng)該停止。注:這個(gè)字段被聲明成 volatile,這對于這個(gè)程序的正確運(yùn)行非常重要。在本章的后面,我們將看到原因。
3、受控訪問的同步
為了確??梢栽诰€程之間以受控方式共享數(shù)據(jù),Java 語言提供了兩個(gè)關(guān)鍵字:synchronized 和 volatile。
Synchronized 有兩個(gè)重要含義:它確保了一次只有一個(gè)線程可以執(zhí)行代碼的受保護(hù)部分(互斥,mutual exclusion 或者說 mutex),而且它確保了一個(gè)線程更改的數(shù)據(jù)對于其它線程是可見的(更改的可見性)。
如果沒有同步,數(shù)據(jù)很容易就處于不一致狀態(tài)。例如,如果一個(gè)線程正在更新兩個(gè)相關(guān)值(比如,粒子的位置和速率),而另一個(gè)線程正在讀取這兩個(gè)值,有可能在第一個(gè)線程只寫了一個(gè)值,還沒有寫另一個(gè)值的時(shí)候,調(diào)度第二個(gè)線程運(yùn)行,這樣它就會看到一個(gè)舊值和一個(gè)新值。同步讓我們可以定義必須原子地運(yùn)行的代碼塊,這樣對于其他線程而言,它們要么都執(zhí)行,要么都不執(zhí)行。
同步的原子執(zhí)行或互斥方面類似于其它操作環(huán)境中的臨界段的概念。
4、確保共享數(shù)據(jù)更改的可見性
同步可以讓我們確保線程看到一致的內(nèi)存視圖。
處理器可以使用高速緩存加速對內(nèi)存的訪問(或者編譯器可以將值存儲到寄存器中以便進(jìn)行更快的訪問)。在一些多處理器體系結(jié)構(gòu)上,如果在一個(gè)處理器的高速緩存中修改了內(nèi)存位置,沒有必要讓其它處理器看到這一修改,直到刷新了寫入器的高速緩存并且使讀取器的高速緩存無效。
這表示在這樣的系統(tǒng)上,對于同一變量,在兩個(gè)不同處理器上執(zhí)行的兩個(gè)線程可能會看到兩個(gè)不同的值!這聽起來很嚇人,但它卻很常見。它只是表示在訪問其它線程使用或修改的數(shù)據(jù)時(shí),必須遵循某些規(guī)則。
Volatile 比同步更簡單,只適合于控制對基本變量(整數(shù)、布爾變量等)的單個(gè)實(shí)例的訪問。當(dāng)一個(gè)變量被聲明成 volatile,任何對該變量的寫操作都會繞過高速緩存,直接寫入主內(nèi)存,而任何對該變量的讀取也都繞過高速緩存,直接取自主內(nèi)存。這表示所有線程在任何時(shí)候看到的 volatile 變量值都相同。
如果沒有正確的同步,線程可能會看到舊的變量值,或者引起其它形式的數(shù)據(jù)損壞。
5、用鎖保護(hù)的原子代碼塊
Volatile 對于確保每個(gè)線程看到最新的變量值非常有用,但有時(shí)我們需要保護(hù)比較大的代碼片段,如涉及更新多個(gè)變量的片段。
同步使用監(jiān)控器(monitor)或鎖的概念,以協(xié)調(diào)對特定代碼塊的訪問。
每個(gè) Java 對象都有一個(gè)相關(guān)的鎖。同一時(shí)間只能有一個(gè)線程持有 Java 鎖。當(dāng)線程進(jìn)入 synchronized 代碼塊時(shí),線程會阻塞并等待,直到鎖可用,當(dāng)它可用時(shí),就會獲得這個(gè)鎖,然后執(zhí)行代碼塊。當(dāng)控制退出受保護(hù)的代碼塊時(shí),即到達(dá)了代碼塊末尾或者拋出了沒有在 synchronized 塊中捕獲的異常時(shí),它就會釋放該鎖。
這樣,每次只有一個(gè)線程可以執(zhí)行受給定監(jiān)控器保護(hù)的代碼塊。從其它線程的角度看,該代碼塊可以看作是原子的,它要么全部執(zhí)行,要么根本不執(zhí)行。
6、簡單的同步示例
使用 synchronized 塊可以讓您將一組相關(guān)更新作為一個(gè)集合來執(zhí)行,而不必?fù)?dān)心其它線程中斷或看到計(jì)算的中間結(jié)果。以下示例代碼將打印“1 0”或“0 1”。如果沒有同步,它還會打印“1 1”(或“0 0”,隨便您信不信)。
public class SyncExample {
private static lockObject = new Object();
private static class Thread1 extends Thread {
public void run() {
synchronized (lockObject) {
x = y = 0;
System.out.println(x);
}
}
}
private static class Thread2 extends Thread {
public void run() {
synchronized (lockObject) {
x = y = 1;
System.out.println(y);
}
}
}
public static void main(String[] args) {
new Thread1().run();
new Thread2().run();
}
}
在這兩個(gè)線程中都必須使用同步,以便使這個(gè)程序正確工作。
7、Java 鎖定
Java 鎖定合并了一種互斥形式。每次只有一個(gè)線程可以持有鎖。鎖用于保護(hù)代碼塊或整個(gè)方法,必須記住是鎖的身份保護(hù)了代碼塊,而不是代碼塊本身,這一點(diǎn)很重要。一個(gè)鎖可以保護(hù)許多代碼塊或方法。
反之,僅僅因?yàn)榇a塊由鎖保護(hù)并不表示兩個(gè)線程不能同時(shí)執(zhí)行該代碼塊。它只表示如果兩個(gè)線程正在等待相同的鎖,則它們不能同時(shí)執(zhí)行該代碼。
在以下示例中,兩個(gè)線程可以同時(shí)不受限制地執(zhí)行 setLastAccess() 中的 synchronized 塊,因?yàn)槊總€(gè)線程有一個(gè)不同的 thingie 值。因此,synchronized 代碼塊受到兩個(gè)正在執(zhí)行的線程中不同鎖的保護(hù)。
public class SyncExample {
public static class Thingie {
private Date lastAccess;
public synchronized void setLastAccess(Date date) {
this.lastAccess = date;
}
}
public static class MyThread extends Thread {
private Thingie thingie;
public MyThread(Thingie thingie) {
this.thingie = thingie;
}
public void run() {
thingie.setLastAccess(new Date());
}
}
public static void main() {
Thingie thingie1 = new Thingie(),
thingie2 = new Thingie();
new MyThread(thingie1).start();
new MyThread(thingie2).start();
}
}
8、同步的方法
創(chuàng)建 synchronized 塊的最簡單方法是將方法聲明成 synchronized。這表示在進(jìn)入方法主體之前,調(diào)用者必須獲得鎖:
public class Point {
public synchronized void setXY(int x, int y) {
this.x = x;
this.y = y;
}
}
對于普通的 synchronized方法,這個(gè)鎖是一個(gè)對象,將針對它調(diào)用方法。對于靜態(tài) synchronized 方法,這個(gè)鎖是與 Class 對象相關(guān)的監(jiān)控器,在該對象中聲明了方法。
僅僅因?yàn)?setXY() 被聲明成 synchronized 并不表示兩個(gè)不同的線程不能同時(shí)執(zhí)行 setXY(),只要它們調(diào)用不同的 Point 實(shí)例的 setXY() 就可同時(shí)執(zhí)行。對于一個(gè) Point 實(shí)例,一次只能有一個(gè)線程執(zhí)行 setXY(),或 Point 的任何其它 synchronized 方法。
9、同步的塊
synchronized 塊的語法比 synchronized 方法稍微復(fù)雜一點(diǎn),因?yàn)檫€需要顯式地指定鎖要保護(hù)哪個(gè)塊。Point 的以下版本等價(jià)于前一頁中顯示的版本:
public class Point {
public void setXY(int x, int y) {
synchronized (this) {
this.x = x;
this.y = y;
}
}
}
使用 this 引用作為鎖很常見,但這并不是必需的。這表示該代碼塊將與這個(gè)類中的 synchronized 方法使用同一個(gè)鎖。
由于同步防止了多個(gè)線程同時(shí)執(zhí)行一個(gè)代碼塊,因此性能上就有問題,即使是在單處理器系統(tǒng)上。最好在盡可能最小的需要保護(hù)的代碼塊上使用同步。
訪問局部(基于堆棧的)變量從來不需要受到保護(hù),因?yàn)樗鼈冎荒鼙蛔约核鶎俚木€程訪問。
10、大多數(shù)類并沒有同步
因?yàn)橥綍硇⌒〉男阅軗p失,大多數(shù)通用類,如 java.util 中的 Collection 類,不在內(nèi)部使用同步。這表示在沒有附加同步的情況下,不能在多個(gè)線程中使用諸如 HashMap 這樣的類。
總結(jié)
以上是生活随笔為你收集整理的java 访问线程_java线程简介(共享对数据的访问)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 360积分有什么用?
- 下一篇: java消息 框架_java 框架-消息