Java基礎 - Synchronized與Lock鎖的區別

wo8553456 8年前發布 | 10K 次閱讀 JVM Java開發

楔子

最近一直都比較忙,沒有時間寫博客了。今天項目終于灰度了,可以有時間寫寫博客,看看文章了!!!╮(╯▽╰)╭

今天要寫的主題是Java的基礎知識,Synchronized和Lock鎖的區別!!!

區別

1、ReentrantLock擁有Synchronized相同的并發性和內存語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候等特性。

線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定

如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷

如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以后,中斷等待,而干別的事情

ReentrantLock獲取鎖定與三種方式:

  • lock(), 如果獲取了鎖立即返回,如果別的線程持有鎖,當前線程則一直處于休眠狀態,直到獲取鎖

  • tryLock(), 如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;

  • tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;

  • lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前線程處于休眠狀態,直到或者鎖定,或者當前線程被別的線程中斷

2、synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過代碼實現的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中

3、在資源競爭不是很激烈的情況下,Synchronized的性能要優于ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態;

5.0的多線程任務包對于同步的性能方面有了很大的改進,在原有synchronized關鍵字的基礎上,又增加了ReentrantLock,以及各種Atomic類。了解其性能的優劣程度,有助與我們在特定的情形下做出正確的選擇。

簡單的總結

  • synchronized:

    在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在于,編譯程序通常會盡可能的進行優化synchronize,另外可讀性非常好,不管用沒用過5.0多線程包的程序員都能理解。

  • ReentrantLock:

    ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,性能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的性能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。

  • Atomic:

    和上面的類似,不激烈情況下,性能比synchronized略遜,而激烈的時候,也能維持常態。激烈的時候,Atomic的性能會優于ReentrantLock一倍左右。但是其有一個缺點,就是只能同步一個值,一段代碼中只能出現一個Atomic的變量,多于一個同步無效。因為他不能在多個Atomic之間同步。

所以,我們寫同步的時候,優先考慮synchronized,如果有特殊需要,再進一步優化。ReentrantLock和Atomic如果用的不好,不僅不能提高性能,還可能帶來災難。

測試結果

先貼測試結果:再貼代碼(Atomic測試代碼不準確,一個同步中只能有1個Actomic,這里用了2個,但是這里的測試只看速度)

round:100000 thread:5

Sync = 35301694

Lock = 56255753

Atom = 43467535

round:200000 thread:10

Sync = 110514604

Lock = 204235455

Atom = 170535361

round:300000 thread:15

Sync = 253123791

Lock = 448577123

Atom = 362797227

round:400000 thread:20

Sync = 16562148262

Lock = 846454786

Atom = 667947183

round:500000 thread:25

Sync = 26932301731

Lock = 1273354016

Atom = 982564544

Java代碼

package test.thread;

import static java.lang.System.out;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

public class TestSyncMethods {

public static void test(int round,int threadNum,CyclicBarrier cyclicBarrier){     
    new SyncTest("Sync",round,threadNum,cyclicBarrier).testTime();     
    new LockTest("Lock",round,threadNum,cyclicBarrier).testTime();     
    new AtomicTest("Atom",round,threadNum,cyclicBarrier).testTime();     
}     

public static void main(String args[]){     

    for(int i=0;i<5;i++){     
        int round=100000*(i+1);     
        int threadNum=5*(i+1);     
        CyclicBarrier cb=new CyclicBarrier(threadNum*2+1);     
        out.println("==========================");     
        out.println("round:"+round+" thread:"+threadNum);     
        test(round,threadNum,cb);     

    }     
}     

}

class SyncTest extends TestTemplate{
public SyncTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){
super( _id, _round, _threadNum, _cb);
}
@Override
/**

 * synchronized關鍵字不在方法簽名里面,所以不涉及重載問題   
 */    
synchronized long  getValue() {     
    return super.countValue;     
}     
@Override    
synchronized void  sumValue() {     
    super.countValue+=preInit[index++%round];     
}     

}

class LockTest extends TestTemplate{
ReentrantLock lock=new ReentrantLock();
public LockTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){
super( _id, _round, _threadNum, _cb);
}
/**

 * synchronized關鍵字不在方法簽名里面,所以不涉及重載問題   
 */    
@Override    
long getValue() {     
    try{     
        lock.lock();     
        return super.countValue;     
    }finally{     
        lock.unlock();     
    }     
}     
@Override    
void sumValue() {     
    try{     
        lock.lock();     
        super.countValue+=preInit[index++%round];     
    }finally{     
        lock.unlock();     
    }     
}     

}

class AtomicTest extends TestTemplate{
public AtomicTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){
super( _id, _round, _threadNum, _cb);
}
@Override
/**

 * synchronized關鍵字不在方法簽名里面,所以不涉及重載問題   
 */    
long  getValue() {     
    return super.countValueAtmoic.get();     
}     
@Override    
void  sumValue() {     
    super.countValueAtmoic.addAndGet(super.preInit[indexAtomic.get()%round]);     
}     

}
abstract class TestTemplate{
private String id;
protected int round;
private int threadNum;
protected long countValue;
protected AtomicLong countValueAtmoic=new AtomicLong(0);
protected int[] preInit;
protected int index;
protected AtomicInteger indexAtomic=new AtomicInteger(0);
Random r=new Random(47);
//任務柵欄,同批任務,先到達wait的任務掛起,一直等到全部任務到達制定的wait地點后,才能全部喚醒,繼續執行
private CyclicBarrier cb;
public TestTemplate(String _id,int _round,int _threadNum,CyclicBarrier _cb){
this.id=_id;
this.round=_round;
this.threadNum=_threadNum;
cb=_cb;
preInit=new int[round];
for(int i=0;i<preInit.length;i++){
preInit[i]=r.nextInt(100);
}
}

abstract void sumValue();     
/*   
 * 對long的操作是非原子的,原子操作只針對32位   
 * long是64位,底層操作的時候分2個32位讀寫,因此不是線程安全   
 */    
abstract long getValue();     

public void testTime(){     
    ExecutorService se=Executors.newCachedThreadPool();     
    long start=System.nanoTime();     
    //同時開啟2*ThreadNum個數的讀寫線程     
    for(int i=0;i<threadNum;i++){     
        se.execute(new Runnable(){     
            public void run() {     
                for(int i=0;i<round;i++){     
                    sumValue();     
                }     

                //每個線程執行完同步方法后就等待     
                try {     
                    cb.await();     
                } catch (InterruptedException e) {     
                    // TODO Auto-generated catch block     
                    e.printStackTrace();     
                } catch (BrokenBarrierException e) {     
                    // TODO Auto-generated catch block     
                    e.printStackTrace();     
                }     


            }     
        });     
        se.execute(new Runnable(){     
            public void run() {     

                getValue();     
                try {     
                    //每個線程執行完同步方法后就等待     
                    cb.await();     
                } catch (InterruptedException e) {     
                    // TODO Auto-generated catch block     
                    e.printStackTrace();     
                } catch (BrokenBarrierException e) {     
                    // TODO Auto-generated catch block     
                    e.printStackTrace();     
                }     

            }     
        });     
    }     

    try {     
        //當前統計線程也wait,所以CyclicBarrier的初始值是threadNum*2+1     
        cb.await();     
    } catch (InterruptedException e) {     
        // TODO Auto-generated catch block     
        e.printStackTrace();     
    } catch (BrokenBarrierException e) {     
        // TODO Auto-generated catch block     
        e.printStackTrace();     
    }     
    //所有線程執行完成之后,才會跑到這一步     
    long duration=System.nanoTime()-start;     
    out.println(id+" = "+duration);     

}     

}</code></pre>

補充知識

CyclicBarrier和CountDownLatch一樣,都是關于線程的計數器。

  • CyclicBarrier初始化時規定一個數目,然后計算調用了CyclicBarrier.await()進入等待的線程數。當線程數達到了這個數目時,所有進入等待狀態的線程被喚醒并繼續。
  • CyclicBarrier就象它名字的意思一樣,可看成是個障礙, 所有的線程必須到齊后才能一起通過這個障礙。
  • CyclicBarrier初始時還可帶一個Runnable的參數, 此Runnable任務在CyclicBarrier的數目達到后,所有其它線程被喚醒前被執行。

 

來自:http://hanhailong.com/2016/12/10/Synchronized與Lock鎖的區別/

 

 本文由用戶 wo8553456 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!