本文目錄一覽:
- 1、java 多線程監聽數據表的改變,怎麼解決同步問題
- 2、java多線程解決同步問題的幾種方式,原理和代碼
- 3、java類內多個函數如何同步
- 4、關於java的同步問題
- 5、java多線程如何解決同步的安全問題
- 6、Java如何處理多線程的數據同步問題
java 多線程監聽數據表的改變,怎麼解決同步問題
噢,是這樣的,不是什麼東西都能當做鎖,你不能這樣理解。
synchronized(obj),obj這裡是你所在類的MIDlet的一個實例對象。
目的是解決因線程不同步而對數據造成破壞的問題。
假如:在一個類中有一個成員變數a,還有兩個線程,如果線程不同步的話,這兩個線程有可能同時訪問同一個變數a,這樣的話,就會出現問題,最後執的結果a到底是幾呢,所以就要使用線程同步這個辦法了。
使用線程同步後,線程1在訪問a的時候,我加了一把鎖,在這個時候別的線程是不允許訪問a的,等線程1對a有訪問結束後,就會去掉這把鎖,其他的線程再訪問a的時候,又會加鎖,這樣在同一時候,只能有一方訪問a,這樣就不會出現問題,我說這麼多,你明白了嗎?希望你能明白,不然我說的算白說了,呵呵!!!!
java多線程解決同步問題的幾種方式,原理和代碼
在Java中一共有四種方法支持同步,其中前三個是同步方法,一個是管道方法。管道方法不建議使用。
wait()/notify()方法
await()/signal()方法
BlockingQueue阻塞隊列方法
PipedInputStream/PipedOutputStream
阻塞隊列的一個簡單實現:
public class BlockingQueue {
private List queue = new LinkedList();
private int limit = 10;
public BlockingQueue(int limit){
this.limit = limit;
}
public synchronized void enqueue(Object item)throws InterruptedException {
while(this.queue.size() == this.limit) {
wait();
}
if(this.queue.size() == 0) {
notifyAll();
}
this.queue.add(item);
}
public synchronized Object dequeue() throws InterruptedException{
while(this.queue.size() == 0){
wait();
}
if(this.queue.size() == this.limit){
notifyAll();
}
return this.queue.remove(0);
}}
在enqueue和dequeue方法內部,只有隊列的大小等於上限(limit)或者下限(0)時,才調用notifyAll方法。如果隊列的大小既不等於上限,也不等於下限,任何線程調用enqueue或者dequeue方法時,都不會阻塞,都能夠正常的往隊列中添加或者移除元素。
wait()/notify()方法
生產者的主要作用是生成一定量的數據放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入數據,消費者也不會在緩衝區中空時消耗數據。
要解決該問題,就必須讓生產者在緩衝區滿時休眠(要麼乾脆就放棄數據),等到下次消費者消耗緩衝區中的數據的時候,生產者才能被喚醒,開始往緩衝區添加數據。同樣,也可以讓消費者在緩衝區空時進入休眠,等到生產者往緩衝區添加數據之後,再喚醒消費者。
java類內多個函數如何同步
線程間的通訊首要的方式就是對欄位及其欄位所引用的對象的共享訪問。這種通信方式是及其高效的,但是也是導致了可能的錯誤:線程間相互干涉和內存一致性的問題。避免出現這兩種錯誤的方法就是同步。
線程間相互干擾描述了當多個線程訪問共享數據時可能出現的錯誤。
內存一致性錯誤描述的了共享內存可能導致的錯誤。
同步方法(Synchronized method)描述了一種簡單的可以有效防止線程間相互干擾及其內存一致性錯誤的方法。
明鎖及同步描述了一種更加通用的同步方法,以及同步是如何基於明鎖而實現的。
原子性描述了不能被其它線程干擾的操作。
線程間的相互干擾
考慮下面的一個簡單的類Counter:
[java] view plaincopy
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 plaincopy
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 plaincopy
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 plaincopy
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中進行討論。
關於java的同步問題
查表歸查表,在插入數據的時候再去查表,你把插入數據跟查表操作放在synchronized(java的同步的關鍵字,可以去看下用法)的函數裡面,這樣可以保證插入的編號不會一樣。。。
要不然你就把這個給資料庫來管理,比如oracle的sequence,sql server的自增
PS: synchronized肯定是針對不同對象的,同一對象間是不需要考慮同步問題的。
另外,java的synchronized是絕對不可能發生死鎖的,已經提供了內置的防死鎖功能,餓死的情況倒有可能。
java多線程如何解決同步的安全問題
你的編程思想還有待進步。
同步的變數或操作不要寫在線程里。 你在你的Test里把這些讀取,加減,分別封裝好,在這些讀取,加減的方法上加synchronized 就行了。
Java如何處理多線程的數據同步問題
通過synchronize加鎖進行實現進行之間的互斥、通過wait、notify方法實現線程之間的同步。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/257270.html