Ⅰ java中線程同步的幾種方法
線程同步主要有以下種方法(示例中是實現計數的功能):
1、同步方法,即使用synchronized關鍵字修飾方法,例如:
publicsynchronizedvoidadd(intc){...}
2、同步代碼塊,即有synchronized關鍵字修飾的語句塊,例如:
publicvoidaddAndGet(intc){
synchronized(this){
count+=c;
}
}
3、使用特殊域變數(volatile)實現線程同步,該方法不能保證絕對的同步。
例如:privatevolatileintcount=0;
4、使用鎖實現線程同步,例如:
privateLocklock=newReentrantLock();
publicvoidadd(intc){
lock.lock();//上鎖
try{
count+=c;
}finally{
lock.unlock();//解鎖
}
}
5、使用原子變數實現線程同步,在java的util.concurrent.atomic包中提供了創建了原子類型變數的工具類,例如:
privateAtomicIntegercount=newAtomicInteger(1);
publicvoidadd(intc){
count.addAndGet(c);
}
6、使用局部變數實現線程同步,如果使用ThreadLocal管理變數,則每一個使用該變數的線程都獲得該變數的副本, 副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變數副本,而不會對其他線程產生影響。
ThreadLocal 類的常用方法
new ThreadLocal<T>() : 創建一個線程本地變數
get() : 返回此線程局部變數的當前線程副本中的值
initialValue() : 返回此線程局部變數的當前線程的"初始值"
set(T value) : 將此線程局部變數的當前線程副本中的值設置為value
示例代碼:
privatestaticThreadLocal<Integer>count=newThreadLocal<Integer>(){
@Override
protectedIntegerinitialValue(){
return1;
}
};
publicvoidadd(intc){
count.set(count.get()+c);
}
7、使用阻塞隊列實現,例如LinkedBlockingQueue,具體使用可網路LinkedBlockingQueue的用法或查看java文檔。
Ⅱ Java類的實例化順序是什麼樣的Java線程同步的方式有哪些
引言:java是在1990年初 ,被詹姆斯•高斯林等人開發的一門面向對象的編程語言。起初,java被稱為0ak,來經過發展0ak改名為java,與1995年的五月份正式向大家發布。
java的實例化順序在繼承沒有的情況
單獨一個類的場景下,初始化順序為依次為靜態數據,繼承的基類的構造函數,成員變數,被調用的構造函數。
其中靜態數據只會初始化一次。(靜態數據包括靜態代碼塊和靜態變數,每個類的靜態數據只會初始化一次)
在繼承的情況下
添加兩個基類,讓繼承父親,父親繼承祖父。
繼承的情況就比較復雜了。由繼承了基類,還將往上回溯,遞歸地調用基類的無參構造方法。
在我們的例子中,在初始化靜態數據後,會先往上追溯,調用父的默認構造方法,此時再往上追溯到爺爺的默認構造方法。
無論是java還是什麼別的東西他都體現了現代社會與信息技術的不斷發展,人們在進行進行技術開發時也有了越來越多的方法。程序類的工作也有了更為快捷的方法,這為信息技術的發展也提供了更好的發展方法
Ⅲ java中什麼同步什麼是非同步分別用在什麼地方
java同步指的是synchronized機制,而非synchronized的都是非同步,弄懂同步的概念就大致明白了兩者的差別。
有關同步:
synchronized用來修飾一個方法或者一個代碼塊,它用來保證在同一時刻最多隻有一個線程執行該段代碼。
一、當兩個並發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
五、以上規則對其它對象鎖同樣適用。
示例代碼:
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
結果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
Ⅳ java類內多個函數如何同步
線程間的通訊首要的方式就是對欄位及其欄位所引用的對象的共享訪問。這種通信方式是及其高效的,但是也是導致了可能的錯誤:線程間相互干涉和內存一致性的問題。避免出現這兩種錯誤的方法就是同步。
線程間相互干擾描述了當多個線程訪問共享數據時可能出現的錯誤。
內存一致性錯誤描述的了共享內存可能導致的錯誤。
同步方法(Synchronized method)描述了一種簡單的可以有效防止線程間相互干擾及其內存一致性錯誤的方法。
明鎖及同步描述了一種更加通用的同步方法,以及同步是如何基於明鎖而實現的。
原子性描述了不能被其它線程干擾的操作。
線程間的相互干擾
考慮下面的一個簡單的類Counter:
[java] view plain
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
不難看出,其中的increment()方法用來對c加1;decrement()方法用來對c減1。然而,有多個線程中都存在對某個Counter對象的引用,那麼線程間的干擾就可能導致出現我們不想要的結果。
線程間的干擾出現在多個線程對同一個數據進行多個操作的時候,也就是出現了「交錯」。山純局這就意味著操作是由多個步驟構成的,而此時,褲仿在這多個步驟的執行上出現了疊加。
Counter類對象的操作貌似不可能出現這種「交錯」,因為其中的兩個關於c的操作都很簡單,只有一條語句。然而,即使是一條語句也是會被VM翻譯成多個步驟的。在這里,我們不深究VM具體上上面的操作翻譯成了什麼樣的步驟。只需要知道即使簡單的c++這樣的表達式也是會被翻譯成三個步驟的:
1. 獲取c的當前值。
2. 對其當前值加1。
3. 將增加後的值存儲到c中。
表達式c--也是會被按照同樣的方式進行翻譯,只不過第二步變成了減1,而不是加1。
假定線程A中調用increment方法,線逗讓程B中調用decrement方法,而調用時間基本上相同。如果c的初始值為0,那麼這兩個操作的「交錯」順序可能如下:
1. 線程A:獲取c的值。
2. 線程B:獲取c的值。
3. 線程A:對獲取到的值加1;其結果是1。
4. 線程B:對獲取到的值減1;其結果是-1。
5. 線程A:將結果存儲到c中;此時c的值是1。
6. 線程B:將結果存儲到c中;此時c的值是-1。
這樣線程A計算的值就丟失了,也就是被線程B的值覆蓋了。上面的這種「交錯」只是一種可能性。在不同的系統環境中,有可能是B線程的結果丟失了,或者是根本就不會出現錯誤。由於這種「交錯」是不可預測的,線程間相互干擾造成的缺陷是很難定位和修改的。
內存一致性錯誤
內存一致性錯誤發生在不同線程對同一數據產生不同的「見解」。導致內存一致性錯誤的原因很負責,超出了本文的描述范圍。慶幸的是,程序員並不需要知道出現這些原因的細節。我們需要的只是一種可以避免這種錯誤的方法。
避免出現內存一致性錯誤的關鍵在於理解「先後順序」關系。這種關系是一種簡單的方法,能夠確保一條語句對內存的寫操作對於其它特定的語句都是可見的。為了理解這點,我們可以考慮如下的示例。假定定義了一個簡單的int類型的欄位並對其進行了初始化:
int counter = 0;
該欄位由兩個線程共享:A和B。假定線程A對counter進行了自增操作:
counter++;
然後,線程B輸出counter的值:
System.out.println(counter);
如果以上兩條語句是在同一個線程中執行的,那麼輸出的結果自然是1。但是如果這兩條語句是在兩個不同的線程中,那麼輸出的結構有可能是0。這是因為沒有保證線程A對counter的修改對線程B來說是可見的。除非程序員在這兩條語句間建立了一定的「先後順序」。
我們可以採取多種方式建立這種「先後順序」。使用同步就是其中之一,這點我們將會在下面的小節中看到。
到目前為止,我們已經看到了兩種建立這種「先後順序」的方式:
當一條語句中調用了Thread.start()方法,那麼每一條和該語句已經建立了「先後順序」的語句都和新線程中的每一條語句有著這種「先後順序」。引入並創建這個新線程的代碼產生的結果對該新線程來說都是可見的。
當一個線程終止了並導致另外的線程中調用join的語句回,那麼此時這個終止了的線程中執行了的所有語句都和隨後的join語句隨後的所有語句建立了這種「先後關系」。也就是說終止了的線程中的代碼效果對調用join方法的線程來說是可見。
關於哪些操作可以建立這種「先後關系」,更多的信息請參閱「java.util.concurrent包的概要說明」。
同步方法
Java編程語言中提供了兩種基本的同步用語:同步方法和同步語句。同步語句相對而言更為復雜一些,我們將在下一小節中進行描述。本節重點討論同步方法。
我們只需要在聲明方法的時候增加關鍵字synchronized即可:
[java] view plain
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
如果count 是SynchronizedCounter類的實例,設置其方法為同步方法將有一下兩個效果:
首先,不可能出現對同一對象的同步方法的兩個調用的「交錯」。當一個線程在執行一個對象的同步方式的時候,其他所有的調用該對象的同步方法的線程對會被掛起,直到第一個線程對該對象操作完畢。
其次,當一個同步方法退出時,會自動與該對象的後續同步方法間建立「先後順序」的關系。這就確保了對該對象的修改對其他線程是可見的。
注意:構造函數不能為同步的——在構造函數前使用synchronized關鍵字將導致語義錯誤。同步構造函數是沒有意義的。這是因為只有創建該對象的線程才能調用其構造函數。
警告:在創建多個線程共享的對象時,要特別小心對該對象的引用不能過早地「泄露」。例如,假定我們想要維護一個保存類的所有實例的列表instances。我們可能會在構造函數中這樣寫到:
instances.add(this);
但是,其他線程可會在該對象的構造完成之前就訪問該對象。
同步方法是一種簡單的可以避免線程相互干擾和內存一致性錯誤的策略:如果一個對象對多個線程都是可見的,那麼所有對該對象的變數的讀寫都應該是通過同步方法完成的(一個例外就是final欄位,他在對象創建完成後是不能被修改的,因此,在對象創建完畢後,可以通過非同步的方法對其進行安全的讀取)。這種策略是有效的,但是可能導致「liveness」問題。這點我們會在本課程的後面進行描述。
內部鎖及同步
同步是構建在被稱為「內部鎖或者是監視鎖」的內部實體上的。(在API中通常被稱為是「監視器」。)內部鎖在兩個方面都扮演著重要的角色:保證對對象訪問的排他性和建立也對象可見性相關的重要的「先後順序」。
每一個對象都有一個與之相關聯動的內部鎖。按照傳統的做法,當一個線程需要對一個對象的欄位進行排他性訪問並保持訪問的一致性時,他必須在訪問前先獲取該對象的內部鎖,然後才能訪問之,最後釋放該內部鎖。在線程獲取對象的內部鎖到釋放對象的內部鎖的這段時間,我們說該線程擁有該對象的內部鎖。只要有一個線程已經擁有了一個內部鎖,其他線程就不能在擁有該鎖了。其他線程將會在試圖獲取該鎖的時候被阻塞了。
當一個線程釋放了一個內部鎖,那麼就會建立起該動作和後續獲取該鎖之間的「先後順序」。
同步方法中的鎖
當一個線程調用一個同步方法的時候,他就自動地獲得了該方法所屬對象的內部鎖,並在方法返回的時候釋放該鎖。即使是由於出現了沒有被捕獲的異常而導致方法返回,該鎖也會被釋放。
我們可能會感到疑惑:當調用一個靜態的同步方法的時候會怎樣了,靜態方法是和類相關的,而不是和對象相關的? 在這種情況下,線程獲取的是該類的類對象的內部鎖。這樣對於靜態欄位的方法是通過一個和類的實例的鎖相區分的另外的鎖來進行的。
同步語句
另外一種創建同步代碼的方式就是使用同步語句。和同步方法不同,使用同步語句是必須指明是要使用哪個對象的內部鎖:
[java] view plain
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
在上面的示例中,方法addName需要對lastName和nameCount的修改進行同步,還要避免同步調用其他對象的方法(在同步代碼段中調用其他對象的方法可能導致「Liveness」中描述的問題)。如果沒有使用同步語句,那麼將不得不使用一個單獨的,未同步的方法來完成對nameList.add的調用。
在改善並發性時,巧妙地使用同步語句能起到很大的幫助作用。例如,我們假定類MsLunch有兩個實例欄位,c1和c2,這兩個變數絕不會一起使用。所有對這兩個變數的更新都需要進行同步。但是沒有理由阻止對c1的更新和對c2的更新出現交錯——這樣做會創建不必要的阻塞,進而降低並發性。此時,我們沒有使用同步方法或者使用和this相關的鎖,而是創建了兩個單獨的對象來提供鎖。
[java] view plain
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
採用這種方式時需要特別的小心。我們必須絕對確保相關欄位的訪問交錯是完全安全的。
同步的重入
回憶前面提到的:線程不能獲取已經被別的線程獲取的鎖。單絲線程可以獲取自身已經擁有的鎖。允許一個線程能重復獲得同一個鎖就稱為同步重入。它是這樣的一種情況:在同步代碼中直接或者間接地調用了還有同步代碼的方法,兩個同步代碼段中使用的是同一個鎖。如果沒有同步重入,在編寫同步代碼時需要額外的小心,以避免線程將自己阻塞。
原子性
在編程中,原子性動作就是指一次性有效完成的動作。原子性動作是不能在中間停止的:要麼一次性完全執行完畢,要麼就不執行。在動作沒有執行完畢之前,是不會產生可見結果的。
通過前面的示例,我們已經發現了諸如c++這樣的自增表達式並不屬於原子操作。即使是非常見到的表達式也定義了負責的動作,這些動作可以被解釋成許多別的動作。然而,的確存在一些原子操作的:
對幾乎所有的原生數據類型變數的讀寫(除了long和都變了外)以及引用變數的讀寫都是原子的。
對所有聲明為volatile的變數的讀寫都是原子的,包括long和double類型。
原子性動作是不會出現交錯的,因此,使用這些原子性動作時不用考慮線程間的干擾。然而,這並不意味著可以移除對原子操作的同步。因為內存一致性錯誤還是有可能出現的。使用volatile變數可以減少內存一致性錯誤的風險,因為任何對volatile變數的寫操作都和後續對該變數的讀操作建立了「先後順序」。這就意味著對volatile類型變數的修改對於別的線程來說是可見的。更重要的是,這意味著當一個線程讀取一個volatile類型的變數是,他看到的不僅僅是對該變數的最後一次修改,還看到了導致這種修改的代碼帶來的其他影響。
使用簡單的原子變數訪問比通過同步代碼來訪問變數更高效,但是需要程序員的更多細心考慮,以避免內存一致性錯誤。這種額外的付出是否值得完全取決於應用程序的大小和復雜度。
在包java.util.concurrent中的一些類提供了原子方法,這些方法不依賴於同步。我們會在章節:High Level Concurrency Objects中進行討論。