https://www.cnblogs.com/duanxz/p/3709608.html?utm_source=tuicool&utm_medium=referral
同步的方法:
一、同步方法
即有synchronized關(guān)鍵字修飾的方法。?由于java的每個對象都有一個內(nèi)置鎖,當用此關(guān)鍵字修飾方法時,?內(nèi)置鎖會保護整個方法。在調(diào)用該方法前,需要獲得內(nèi)置鎖,否則就處于阻塞狀態(tài)。
注: synchronized關(guān)鍵字也可以修飾靜態(tài)方法,此時如果調(diào)用該靜態(tài)方法,將會鎖住整個類。
二、同步代碼塊
即有synchronized關(guān)鍵字修飾的語句塊。?被該關(guān)鍵字修飾的語句塊會自動被加上內(nèi)置鎖,從而實現(xiàn)同步
? ? 代碼如:?
synchronized(object){
}
注:同步是一種高開銷的操作,因此應(yīng)該盡量減少同步的內(nèi)容。通常沒有必要同步整個方法,使用synchronized代碼塊同步關(guān)鍵代碼即可。

package com.xhj.thread;
? ? /**? ? * 線程同步的運用
? ? *
? ? * @author XIEHEJUN
? ? *
? ? */publicclass SynchronizedThread {
? ? ? ? class Bank {
? ? ? ? ? ? privateintaccount = 100;
? ? ? ? ? ? publicint getAccount() {
? ? ? ? ? ? ? ? return account;
? ? ? ? ? ? }
? ? ? ? ? ? /**? ? ? ? ? ? * 用同步方法實現(xiàn)
? ? ? ? ? ? *
? ? ? ? ? ? * @param money
? ? ? ? ? ? */publicsynchronizedvoidsave(int money) {
? ? ? ? ? ? ? ? account += money;
? ? ? ? ? ? }
? ? ? ? ? ? /**? ? ? ? ? ? * 用同步代碼塊實現(xiàn)
? ? ? ? ? ? *
? ? ? ? ? ? * @param money
? ? ? ? ? ? */publicvoidsave1(int money) {
? ? ? ? ? ? ? ? synchronized(this) {
? ? ? ? ? ? ? ? ? ? account += money;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }


classNewThreadimplements Runnable {
? ? ? ? ? ? private Bank bank;
? ? ? ? ? ? public NewThread(Bank bank) {
? ? ? ? ? ? ? ? this.bank = bank;
? ? ? ? ? ? }
? ? ? ? ? ? @Override
? ? ? ? ? ? publicvoid run() {
? ? ? ? ? ? ? ? for(inti = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? // bank.save1(10);bank.save(10);
? ? ? ? ? ? ? ? ? ? System.out.println(i + "賬戶余額為:" + bank.getAccount());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? /**? ? ? ? * 建立線程,調(diào)用內(nèi)部類
? ? ? ? */publicvoid useThread() {
? ? ? ? ? ? Bank bank =new Bank();
? ? ? ? ? ? NewThread new_thread =new NewThread(bank);
? ? ? ? ? ? System.out.println("線程1");
? ? ? ? ? ? Thread thread1 =new Thread(new_thread);
? ? ? ? ? ? thread1.start();
? ? ? ? ? ? System.out.println("線程2");
? ? ? ? ? ? Thread thread2 =new Thread(new_thread);
? ? ? ? ? ? thread2.start();
? ? ? ? }
? ? ? ? publicstaticvoid main(String[] args) {
? ? ? ? ? ? SynchronizedThread st =new SynchronizedThread();
? ? ? ? ? ? st.useThread();
? ? ? ? }
? ? }

=====================================
示例加講解
同步是多線程中的重要概念。同步的使用可以保證在多線程運行的環(huán)境中,程序不會產(chǎn)生設(shè)計之外的錯誤結(jié)果。同步的實現(xiàn)方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關(guān)鍵字。同步相關(guān)的參見《Synchronized之一:基本使用》
同步方法:給一個方法增加synchronized修飾符之后就可以使它成為同步方法,這個方法可以是靜態(tài)方法和非靜態(tài)方法,但是不能是抽象類的抽象方法,也不能是接口中的接口方法。下面代碼是一個同步方法的示例:

publicsynchronizedvoid aMethod() {
? ? // do something } publicstaticsynchronizedvoid anotherMethod() {
? ? // do something }

線程在執(zhí)行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執(zhí)行完它所調(diào)用的同步方法并從中退出,從而導致它釋放了該對象的同步鎖之后。在一個對象被某個線程鎖定之后,其他線程是可以訪問這個對象的所有非同步方法的。
同步塊:同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊里的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。如果這個方法是靜態(tài)同步方法呢?那么線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應(yīng)的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限于同一個對象之間,所以靜態(tài)同步方法只受它所屬類的其它靜態(tài)同步方法的制約,而跟這個類的實例(對象)沒有關(guān)系。
如果一個對象既有同步方法,又有同步塊,那么當其中任意一個同步方法或者同步塊被某個線程執(zhí)行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執(zhí)行同步塊。
synchronized?關(guān)鍵字用于保護共享數(shù)據(jù)。請大家注意“共享數(shù)據(jù)”,你一定要分清哪些數(shù)據(jù)是共享數(shù)據(jù),請看下面的例子:

publicclassThreadTestimplements Runnable{publicsynchronizedvoid run(){
for(inti=0;i<10;i++) {
System.out.print(" " + i);
}
}publicstaticvoid main(String[] args) {
Runnable r1 =newThreadTest();//也可寫成ThreadTest r1 = new ThreadTest(); Runnable r2 =new ThreadTest();
Thread t1 =new Thread(r1);
Thread t2 =new Thread(r2);
t1.start();
t2.start();
}}

在這個程序中,run()雖然被加上了synchronized?關(guān)鍵字,但保護的不是共享數(shù)據(jù)。因為這個程序中的t1,t2?是兩個對象(r1,r2)的線程。而不同的對象的數(shù)據(jù)是不同的,r1,r2?有各自的run()方法,所以輸出結(jié)果無法預(yù)知。
synchronized的目的是使同一個對象的多個線程,在某個時刻只有其中的一個線程可以訪問這個對象的synchronized?數(shù)據(jù)。每個對象都有一個“鎖標志”,當這個對象的一個線程訪問這個對象的某個synchronized?數(shù)據(jù)時,這個對象的所有被synchronized?修飾的數(shù)據(jù)將被上鎖(因為“鎖標志”被當前線程拿走了),只有當前線程訪問完它要訪問的synchronized?數(shù)據(jù)時,當前線程才會釋放“鎖標志”,這樣同一個對象的其它線程才有機會訪問synchronized?數(shù)據(jù)。
示例3:

publicclassThreadTestimplements Runnable{publicsynchronizedvoid run(){
for(inti=0;i<10;i++){
System.out.print(" " + i);
}
}publicstaticvoid main(String[] args){
Runnable r =new ThreadTest();
Thread t1 =new Thread(r);
Thread t2 =new Thread(r);
t1.start();
t2.start();
}}

如果你運行1000?次這個程序,它的輸出結(jié)果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因為這里的synchronized?保護的是共享數(shù)據(jù)。t1,t2?是同一個對象(r)的兩個線程,當其中的一個線程(例如:t1)開始執(zhí)行run()方法時,由于run()受synchronized保護,所以同一個對象的其他線程(t2)無法訪問synchronized?方法(run?方法)。只有當t1執(zhí)行完后t2?才有機會執(zhí)行。
示例4:

publicclassThreadTestimplements Runnable{publicvoid run(){
? ? synchronized(this){
? ? for(inti=0;i<10;i++){
? ? ? ? System.out.print(" " + i);
? ? }
}
}publicstaticvoid main(String[] args){
? ? Runnable r =new ThreadTest();
? ? Thread t1 =new Thread(r);
? ? Thread t2 =new Thread(r);
? ? t1.start();
? ? t2.start();
}
}? ?

這個程序與示例3?的運行結(jié)果一樣。在可能的情況下,應(yīng)該把保護范圍縮到最小,可以用示例4?的形式,this?代表“這個對象”。沒有必要把整個run()保護起來,run()中的代碼只有一個for循環(huán),所以只要保護for?循環(huán)就可以了。
示例5:

publicclassThreadTestimplements Runnable{publicvoid run(){
for(intk=0;k<5;k++){
System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
}synchronized(this){
for(intk=0;k<5;k++) {
System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
}} }publicstaticvoid main(String[] args){
Runnable r =new ThreadTest();
Thread t1 =newThread(r,"t1_name");
Thread t2 =newThread(r,"t2_name");
t1.start();
t2.start();
} }

運行結(jié)果:
t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4
第一個for?循環(huán)沒有受synchronized?保護。對于第一個for?循環(huán),t1,t2?可以同時訪問。運行結(jié)果表明t1?執(zhí)行到了k=2?時,t2?開始執(zhí)行了。t1?首先執(zhí)行完了第一個for?循環(huán),此時t2還沒有執(zhí)行完第一個for?循環(huán)(t2?剛執(zhí)行到k=2)。t1?開始執(zhí)行第二個for?循環(huán),當t1的第二個for?循環(huán)執(zhí)行到k=1?時,t2?的第一個for?循環(huán)執(zhí)行完了。t2?想開始執(zhí)行第二個for?循環(huán),但由于t1?首先執(zhí)行了第二個for?循環(huán),這個對象的鎖標志自然在t1?手中(synchronized?方法的執(zhí)行權(quán)也就落到了t1?手中),在t1?沒執(zhí)行完第二個for?循環(huán)的時候,它是不會釋放鎖標志的。所以t2?必須等到t1?執(zhí)行完第二個for?循環(huán)后,它才可以執(zhí)行第二個for?循環(huán)。
=====================================
三、wait與notify
wait():使一個線程處于等待狀態(tài),并且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處于睡眠狀態(tài),是一個靜態(tài)方法,調(diào)用此方法要捕捉InterruptedException異常。
notify():喚醒一個處于等待狀態(tài)的線程,注意的是在調(diào)用此方法的時候,并不能確切的喚醒某一個等待狀態(tài)的線程,而是由JVM確定喚醒哪個線程,而且不是按優(yōu)先級。
notifyAll():喚醒所有處入等待狀態(tài)的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
詳細見:wait、notify、notifyAll的使用方法
四、使用特殊域變量(volatile)實現(xiàn)線程同步
見《Java并發(fā)編程之三:volatile關(guān)鍵字解析 轉(zhuǎn)載》
下面使用volatile示例場景是錯誤的:

package com.dxz.volatiledemo;//只給出要修改的代碼,其余代碼與上同class Bank {
? ? // 需要同步的變量加上volatileprivatevolatileintaccount = 100;
? ? publicint getAccount() {
? ? ? ? return account;
? ? }
? ? // 這里不再需要synchronizedpublicvoidsave(int money) {
? ? ? ? account += money;
? ? }
}package com.dxz.volatiledemo;import java.util.concurrent.CountDownLatch;publicclassMyThreadimplements Runnable {
? ? Bank bank;
? ? CountDownLatch cdl;
? ? MyThread(Bank bank, CountDownLatch cdl) {
? ? ? ? this.bank = bank;
? ? ? ? this.cdl = cdl;
? ? }
? ? @Override
? ? publicvoid run() {
? ? ? ? for(inti = 1; i < 101; i++) {
? ? ? ? ? ? bank.save(i);
? ? ? ? }
? ? ? ? cdl.countDown();
? ? }
}package com.dxz.volatiledemo;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;publicclass Client {
? ? publicstaticvoidmain(String[] args)throws InterruptedException {
? ? ? ? intnum = 70;
? ? ? ? ExecutorService es = Executors.newFixedThreadPool(num);
? ? ? ? Bank bank =new Bank();
? ? ? ? CountDownLatch cdl =new CountDownLatch(num);
? ? ? ? for(inti = 0; i
? ? ? ? ? ? MyThread mt =new MyThread(bank, cdl);
? ? ? ? ? ? es.submit(mt);
? ? ? ? ? ? //TimeUnit.SECONDS.sleep(1);? ? ? ? }
? ? ? ? cdl.await();
? ? ? ? es.shutdown();
? ? ? ? System.out.println(bank.getAccount());
? ? }
}

每個線程間隔一秒時,計算結(jié)果是:353600,如果去掉線程間隔讓線程競爭起來,結(jié)果很多時候不一致。驗證了volatile變量的以下特性:
可見性。對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入。
原子性:對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復合操作不具有原子性。
五、使用重入鎖實現(xiàn)線程同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。
? ? ReentrantLock類是可重入、互斥、實現(xiàn)了Lock接口的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,并且擴展了其能力。
?ReenreantLock類的常用方法有:
ReentrantLock() : 創(chuàng)建一個ReentrantLock實例
lock() : 獲得鎖
unlock() : 釋放鎖
注:ReentrantLock()還有一個可以創(chuàng)建公平鎖的構(gòu)造方法,但由于能大幅度降低程序運行效率,不推薦使用?
? ? 例如:?
? ? ? ? 在上面例子的基礎(chǔ)上,改寫后的代碼為:?

//只給出要修改的代碼,其余代碼與上同class Bank {
? ? ? ? ? ? privateintaccount = 100;
? ? ? ? ? ? //需要聲明這個鎖privateLock lock =new ReentrantLock();
? ? ? ? ? ? publicint getAccount() {
? ? ? ? ? ? ? ? return account;
? ? ? ? ? ? }
? ? ? ? ? ? //這里不再需要synchronized publicvoidsave(int money) {
? ? ? ? ? ? ? ? lock.lock();
? ? ? ? ? ? ? ? try{
? ? ? ? ? ? ? ? ? ? account += money;
? ? ? ? ? ? ? ? }finally{
? ? ? ? ? ? ? ? ? ? lock.unlock();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }

?注:關(guān)于Lock對象和synchronized關(guān)鍵字的選擇:
? ? ? ? a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,能夠幫助用戶處理所有與鎖相關(guān)的代碼。?
? ? ? ? b.如果synchronized關(guān)鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼?
? ? ? ? c.如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現(xiàn)死鎖,通常在finally代碼釋放鎖?
六、使用局部變量實現(xiàn)線程同步
? ? 如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。
?? ? ThreadLocal 類的常用方法
ThreadLocal() : 創(chuàng)建一個線程本地變量
get() : 返回此線程局部變量的當前線程副本中的值
initialValue() : 返回此線程局部變量的當前線程的"初始值"
set(T value) : 將此線程局部變量的當前線程副本中的值設(shè)置為value
? ? 例如:?
? ? ? ? 在上面例子基礎(chǔ)上,修改后的代碼為:?

//只改Bank類,其余代碼與上同publicclass Bank{
? ? ? ? ? ? //使用ThreadLocal類管理共享變量accountprivatestaticThreadLocal account =newThreadLocal(){
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? protected Integer initialValue(){
? ? ? ? ? ? ? ? ? ? return100;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? ? ? publicvoidsave(int money){
? ? ? ? ? ? ? ? account.set(account.get()+money);
? ? ? ? ? ? }
? ? ? ? ? ? publicint getAccount(){
? ? ? ? ? ? ? ? return account.get();
? ? ? ? ? ? }
? ? ? ? }

? ? 注:ThreadLocal與同步機制?
? ? ? ? a.ThreadLocal與同步機制都是為了解決多線程中相同變量的訪問沖突問題。?
? ? ? ? b.前者采用以"空間換時間"的方法,后者采用以"時間換空間"的方式
七、使用阻塞隊列實現(xiàn)線程同步
前面5種同步方式都是在底層實現(xiàn)的線程同步,但是我們在實際開發(fā)當中,應(yīng)當盡量遠離底層結(jié)構(gòu)。 使用javaSE5.0版本中新增的java.util.concurrent包將有助于簡化開發(fā)。 本小節(jié)主要是使用LinkedBlockingQueue<E>來實現(xiàn)線程的同步 LinkedBlockingQueue<E>是一個基于已連接節(jié)點的,范圍任意的blocking queue。 隊列是先進先出的順序(FIFO),關(guān)于隊列以后會詳細講解~LinkedBlockingQueue 類常用方法?LinkedBlockingQueue() : 創(chuàng)建一個容量為Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在隊尾添加一個元素,如果隊列滿則阻塞 size() : 返回隊列中的元素個數(shù) take() : 移除并返回隊頭元素,如果隊列空則阻塞代碼實例:?實現(xiàn)商家生產(chǎn)商品和買賣商品的同步
注:BlockingQueue<E>定義了阻塞隊列的常用方法,尤其是四種操作元素的方法,我們要多加注意,當隊列滿或空時:
add()方法會拋出異常
offer()方法返回false
take()方法會阻塞
put()方法會阻塞
7.使用原子變量實現(xiàn)線程同步
需要使用線程同步的根本原因在于對普通變量的操作不是原子的。
那么什么是原子操作呢?原子操作就是指將讀取變量值、修改變量值、保存變量值看成一個整體來操作即-這幾種行為要么同時完成,要么都不完成。在java的util.concurrent.atomic包中提供了創(chuàng)建了原子類型變量的工具類,使用該類可以簡化線程同步。其中AtomicInteger表可以用原子方式更新int的值,可用在應(yīng)用程序中(如以原子方式增加的計數(shù)器),但不能用于替換Integer;可擴展Number,允許那些處理機遇數(shù)字類的工具和實用工具進行統(tǒng)一訪問。
AtomicInteger類常用方法:
AtomicInteger(int initialValue) : 創(chuàng)建具有給定初始值的新的
AtomicIntegeraddAddGet(int dalta) : 以原子方式將給定值與當前值相加
get() : 獲取當前值
代碼實例:
只改Bank類,其余代碼與上面第一個例子同

class Bank {
? ? privateAtomicInteger account =newAtomicInteger(100);
? ? public AtomicInteger getAccount() {
? ? ? ? return account;
? ? }
? ? publicvoidsave(int money) {
? ? ? ? account.addAndGet(money);
? ? }
}
