java多線程同步
㈠ java 實現線程同步的方式有哪些
實現同步機制有兩個方法:
1、同步代碼塊:
synchronized(同一個數據){} 同一個數據:就是N條線程同時訪問一個數據。
2、同步方法:
public synchronized 數據返回類型 方法名(){}
就是使用 synchronized 來修飾某個方法,則該方法稱為同步方法。對於同步方法而言,無需顯示指定同步監視器,同步方法的同步監視器是 this 也就是該對象的本身(這里指的對象本身有點含糊,其實就是調用該同步方法的對象)通過使用同步方法,可非常方便的將某類變成線程安全的類,具有如下特徵:
1,該類的對象可以被多個線程安全的訪問。
2,每個線程調用該對象的任意方法之後,都將得到正確的結果。
3,每個線程調用該對象的任意方法之後,該對象狀態依然保持合理狀態。
註:synchronized關鍵字可以修飾方法,也可以修飾代碼塊,但不能修飾構造器,屬性等。
實現同步機制注意以下幾點: 安全性高,性能低,在多線程用。性能高,安全性低,在單線程用。
1,不要對線程安全類的所有方法都進行同步,只對那些會改變共享資源方法的進行同步。
2,如果可變類有兩種運行環境,當線程環境和多線程環境則應該為該可變類提供兩種版本:線程安全版本和線程不安全版本(沒有同步方法和同步塊)。在單線程中環境中,使用線程不安全版本以保證性能,在多線程中使用線程安全版本.
㈡ java多線程開發的同步機制有哪些
一段synchronized的代碼被一個線程執行之前,他要先拿到執行這段代碼的許可權,在 java里邊就是拿到某個同步對象的鎖(一個對象只有一把鎖); 如果這個時候同步對象的鎖被其他線程拿走了,他(這個線程)就只能等了(線程阻塞在鎖池 等待隊列中)。 取到鎖後,他就開始執行同步代碼(被synchronized修飾的代碼);線程執行完同步代碼後馬上就把鎖還給同步對象,其他在鎖池中 等待的某個線程就可以拿到鎖執行同步代碼了。這樣就保證了同步代碼在統一時刻只有一個線程在執行。
眾所周知,在Java多線程編程中,一個非常重要的方面就是線程的同步問題。
關於線程的同步,一般有以下解決方法:
1. 在需要同步的方法的方法簽名中加入synchronized關鍵字。
2. 使用synchronized塊對需要進行同步的代碼段進行同步。
3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock對象。
另外,為了解決多個線程對同一變數進行訪問時可能發生的安全性問題,我們不僅可以採用同步機制,更可以通過JDK 1.2中加入的ThreadLocal來保證更好的並發性。
㈢ java多線程有幾種實現方法線程之間如何同步
一、為什麼要線程同步
因為當我們有多個線程要同時訪問一個變數或對象時,如果這些線程中既有讀又有寫操作時,就會導致變數值或對象的狀態出現混亂,從而導致程序異常。舉個例子,如果一個銀行賬戶同時被兩個線程操作,一個取100塊,一個存錢100塊。假設賬戶原本有0塊,如果取錢線程和存錢線程同時發生,會出現什麼結果呢?取錢不成功,賬戶余額是100.取錢成功了,賬戶余額是0.那到底是哪個呢?很難說清楚。因此多線程同步就是要解決這個問題。
二、不同步時的代碼
Bank.Java
packagethreadTest;
/**
*@authorww
*
*/
publicclassBank{
privateintcount=0;//賬戶余額
//存錢
publicvoidaddMoney(intmoney){
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}
//取錢
publicvoidsubMoney(intmoney){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}
SyncThreadTest.java
packagethreadTest;
publicclassSyncThreadTest{
publicstaticvoidmain(Stringargs[]){
finalBankbank=newBank();
Threadtadd=newThread(newRunnable(){
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
bank.addMoney(100);
bank.lookMoney();
System.out.println(" ");
}
}
});
Threadtsub=newThread(newRunnable(){
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
bank.subMoney(100);
bank.lookMoney();
System.out.println(" ");
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
});
tsub.start();
tadd.start();
}
}
余額不足
賬戶余額:0
余額不足
賬戶余額:100
1441790503354存進:100
賬戶余額:100
1441790504354存進:100
賬戶余額:100
1441790504354取出:100
賬戶余額:100
1441790505355存進:100
賬戶余額:100
1441790505355取出:100
賬戶余額:100
三、使用同步時的代碼
(1)同步方法:
即有synchronized關鍵字修飾的方法。由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
修改後的Bank.java
packagethreadTest;
/**
*@authorww
*
*/
publicclassBank{
privateintcount=0;//賬戶余額
//存錢
(intmoney){
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}
//取錢
(intmoney){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}
再看看運行結果:
余額不足
賬戶余額:0
余額不足
賬戶余額:0
1441790837380存進:100
賬戶余額:100
1441790838380取出:100
賬戶余額:0
1441790838380存進:100
賬戶余額:100
1441790839381取出:100
賬戶余額:0
瞬間感覺可以理解了吧。
註: synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類
(2)同步代碼塊
即有synchronized關鍵字修飾的語句塊。被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
Bank.java代碼如下:
packagethreadTest;
/**
*@authorww
*
*/
publicclassBank{
privateintcount=0;//賬戶余額
//存錢
publicvoidaddMoney(intmoney){
synchronized(this){
count+=money;
}
System.out.println(System.currentTimeMillis()+"存進:"+money);
}
//取錢
publicvoidsubMoney(intmoney){
synchronized(this){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}
運行結果如下:
余額不足
賬戶余額:0
1441791806699存進:100
賬戶余額:100
1441791806700取出:100
賬戶余額:0
1441791807699存進:100
賬戶余額:100
效果和方法一差不多。
註:同步是一種高開銷的操作,因此應該盡量減少同步的內容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。
(3)使用特殊域變數(volatile)實現線程同步
a.volatile關鍵字為域變數的訪問提供了一種免鎖機制
b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變數
Bank.java代碼如下:
packagethreadTest;
/**
*@authorww
*
*/
publicclassBank{
privatevolatileintcount=0;//賬戶余額
//存錢
publicvoidaddMoney(intmoney){
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}
//取錢
publicvoidsubMoney(intmoney){
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}
運行效果怎樣呢?
余額不足
賬戶余額:0
余額不足
賬戶余額:100
1441792010959存進:100
賬戶余額:100
1441792011960取出:100
賬戶余額:0
1441792011961存進:100
賬戶余額:100
是不是又看不懂了,又亂了。這是為什麼呢?就是因為volatile不能保證原子操作導致的,因此volatile不能代替synchronized。此外volatile會組織編譯器對代碼優化,因此能不使用它就不適用它吧。它的原理是每次要線程要訪問volatile修飾的變數時都是從內存中讀取,而不是存緩存當中讀取,因此每個線程訪問到的變數值都是一樣的。這樣就保證了同步。
(4)使用重入鎖實現線程同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實現了Lock介面的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,並且擴展了其能力。
ReenreantLock類的常用方法有:
ReentrantLock() : 創建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖
註:ReentrantLock()還有一個可以創建公平鎖的構造方法,但由於能大幅度降低程序運行效率,不推薦使用
Bank.java代碼修改如下:
packagethreadTest;
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
/**
*@authorww
*
*/
publicclassBank{
privateintcount=0;//賬戶余額
//需要聲明這個鎖
privateLocklock=newReentrantLock();
//存錢
publicvoidaddMoney(intmoney){
lock.lock();//上鎖
try{
count+=money;
System.out.println(System.currentTimeMillis()+"存進:"+money);
}finally{
lock.unlock();//解鎖
}
}
//取錢
publicvoidsubMoney(intmoney){
lock.lock();
try{
if(count-money<0){
System.out.println("余額不足");
return;
}
count-=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}finally{
lock.unlock();
}
}
//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count);
}
}
運行效果怎麼樣呢?
余額不足
賬戶余額:0
余額不足
賬戶余額:0
1441792891934存進:100
賬戶余額:100
1441792892935存進:100
賬戶余額:200
1441792892954取出:100
賬戶余額:100
效果和前兩種方法差不多。
如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼 。如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally代碼釋放鎖
(5)使用局部變數實現線程同步
Bank.java代碼如下:
packagethreadTest;
/**
*@authorww
*
*/
publicclassBank{
privatestaticThreadLocal<Integer>count=newThreadLocal<Integer>(){
@Override
protectedIntegerinitialValue(){
//TODOAuto-generatedmethodstub
return0;
}
};
//存錢
publicvoidaddMoney(intmoney){
count.set(count.get()+money);
System.out.println(System.currentTimeMillis()+"存進:"+money);
}
//取錢
publicvoidsubMoney(intmoney){
if(count.get()-money<0){
System.out.println("余額不足");
return;
}
count.set(count.get()-money);
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查詢
publicvoidlookMoney(){
System.out.println("賬戶余額:"+count.get());
}
}
運行效果:
余額不足
賬戶余額:0
余額不足
賬戶余額:0
1441794247939存進:100
賬戶余額:100
余額不足
1441794248940存進:100
賬戶余額:0
賬戶余額:200
余額不足
賬戶余額:0
1441794249941存進:100
賬戶余額:300
看了運行效果,一開始一頭霧水,怎麼只讓存,不讓取啊?看看ThreadLocal的原理:
如果使用ThreadLocal管理變數,則每一個使用該變數的線程都獲得該變數的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變數副本,而不會對其他線程產生影響。現在明白了吧,原來每個線程運行的都是一個副本,也就是說存錢和取錢是兩個賬戶,知識名字相同而已。所以就會發生上面的效果。
ThreadLocal與同步機制
a.ThreadLocal與同步機制都是為了解決多線程中相同變數的訪問沖突問題
b.前者採用以"空間換時間"的方法,後者採用以"時間換空間"的方式
㈣ JAVA 多線程同步概念
1
多線程的優勢在於並發
如果有多線程需要共享某個資源
在運行到這里的時候和單線程的效率就相同了
但在運行到這之前都是處於並發的狀態
瓶頸就在於鎖了
但沒有訪問同一個資源時就是並發的效率就高了
2
多線程同時工作就算多線程了吧
偽多線程...你是想說超線程么
㈤ java.關於線程同步的幾個知識點
擴展:
為何要使用同步?
因為java允許多線程並發控制,當多個線程同時操作一個可共享的資源變數時(如數據的增刪改查),所以將會導致數據不準確,相互之間產生沖突,因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調用,從而保證了該變數的唯一性和准確性。
2.同步方法
有synchronized關鍵字修飾的方法。
由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
列:
public synchronized void save(){}
3.同步代碼塊
有synchronized關鍵字修飾的語句塊。
被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
列: synchronized(object){ }
4.用特殊域變數(volatile)實現線程同步
5.用重入鎖實現線程同步
6.用局部變數實現線程同步
㈥ Java線程同步的方法
等待喚醒機制
wait():讓線程等待。將線程存儲到一個線程池中。
notify():喚醒被等待的線程。通常都喚醒線程池中的第一個。讓被喚醒的線程處於臨時阻塞狀態。
notifyAll(): 喚醒所有的等待線程。將線程池中的所有線程都喚醒,讓它們從凍結狀體轉到臨時阻塞狀態.
這三個方法用於操作線程,可是定義在了Object類中,為什麼呢?
因為,這三個方法在使用時,都需要定義在同步中,要明確這些方法所操作的線程所屬於鎖。
簡單說。在A鎖被wait的線程,只能被A鎖的notify方法喚醒。
所以必須要表示wait notify方法所屬的鎖對象,而鎖對象可以是任意的對象。
可以被任意的對象調用的方法肯定定義在Object類中。
注意:等待喚醒機制,通常都用在同步中,因為需要鎖的支持。
而且必須要明確wait notify 所作用的鎖對象。
JDK1.5後的鎖
在jdk1.5版本之後,
出現了一些新的特性,將原理的線程進行了改良。
在java.util.concurrent.locks包中提供了一個介面Lock。替代了synchronized。
synchronized。使用的是鎖操作是隱式的。
Lock介面,使用的鎖操作是顯示的。
由兩個方法來完成:
lock():獲取鎖。
unlock():釋放鎖。
還有一個對象,Condition.
該對象的出現替代了Object中的wait notify notifyAll這些操作監視器的方法。
替代後的方式:await signal signalAll.
㈦ JAVA中線程同步方法有哪些
JAVA中線程同步方法一般有以下三種:
1 wait方法:
該方法屬於Object的方法,wait方法的作用是使得當前調用wait方法所在部分(代碼塊)的線程停止執行,並釋放當前獲得的調用wait所在的代碼塊的鎖,並在其他線程調用notify或者notifyAll方法時恢復到競爭鎖狀態(一旦獲得鎖就恢復執行)。
調用wait方法需要注意幾點:
第一點:wait被調用的時候必須在擁有鎖(即synchronized修飾的)的代碼塊中。
第二點:恢復執行後,從wait的下一條語句開始執行,因而wait方法總是應當在while循環中調用,以免出現恢復執行後繼續執行的條件不滿足卻繼續執行的情況。
第三點:若wait方法參數中帶時間,則除了notify和notifyAll被調用能激活處於wait狀態(等待狀態)的線程進入鎖競爭外,在其他線程中interrupt它或者參數時間到了之後,該線程也將被激活到競爭狀態。
第四點:wait方法被調用的線程必須獲得之前執行到wait時釋放掉的鎖重新獲得才能夠恢復執行。
2 notify方法和notifyAll方法:
notify方法通知調用了wait方法,但是尚未激活的一個線程進入線程調度隊列(即進入鎖競爭),注意不是立即執行。並且具體是哪一個線程不能保證。另外一點就是被喚醒的這個線程一定是在等待wait所釋放的鎖。
notifyAll方法則喚醒所有調用了wait方法,尚未激活的進程進入競爭隊列。
3 synchronized關鍵字:
第一點:synchronized用來標識一個普通方法時,表示一個線程要執行該方法,必須取得該方法所在的對象的鎖。
第二點:synchronized用來標識一個靜態方法時,表示一個線程要執行該方法,必須獲得該方法所在的類的類鎖。
第三點:synchronized修飾一個代碼塊。類似這樣:synchronized(obj) { //code.... }。表示一個線程要執行該代碼塊,必須獲得obj的鎖。這樣做的目的是減小鎖的粒度,保證當不同塊所需的鎖不沖突時不用對整個對象加鎖。利用零長度的byte數組對象做obj非常經濟。
㈧ 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);
- }}
wait()/notify()方法
生產者的主要作用是生成一定量的數據放到緩沖區中,然後重復此過程。與此同時,消費者也在緩沖區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩沖區滿時加入數據,消費者也不會在緩沖區中空時消耗數據。
要解決該問題,就必須讓生產者在緩沖區滿時休眠(要麼乾脆就放棄數據),等到下次消費者消耗緩沖區中的數據的時候,生產者才能被喚醒,開始往緩沖區添加數據。同樣,也可以讓消費者在緩沖區空時進入休眠,等到生產者往緩沖區添加數據之後,再喚醒消費者。
阻塞隊列的一個簡單實現:
在enqueue和dequeue方法內部,只有隊列的大小等於上限(limit)或者下限(0)時,才調用notifyAll方法。如果隊列的大小既不等於上限,也不等於下限,任何線程調用enqueue或者dequeue方法時,都不會阻塞,都能夠正常的往隊列中添加或者移除元素。