synchronized關鍵字的使用
多線程的不安全性,所以有了鎖的概念,在ThreadA操作對象時,其他線程都不能操作此對象,待ThreadA釋放鎖後,其他線程中的某一個線程才被允許操作此對象。
synchronized是JVM層面的鎖,因爲synchronized是關鍵字,JVM封裝了他所具有的功能。
下面我們就看一看synchronized的使用
1、修飾實例方法
2、修飾靜態方法
3、修飾代碼塊
//靜態方法
public static synchronized void incr(){
count ++;
}
//實例方法
public synchronized void incr2(){
count ++;
}
//代碼塊
public void incr3(){
synchronized (SyncDemo.class){
count ++;
}
}
synchroinzed的使用總結:
- 兩種作用範圍(類鎖 / 對象鎖)
- 兩種表現形式(方法上 / 代碼塊)
區別:跨對象跨線程訪問
兩種特性:共享條件(鎖共享) / 互斥條件(代碼塊執行互斥)
線程給我們帶來的好處
多線程可以提高現在多核多線程CPU的利用率。
多線程可以幫助我們更好的優化程序,提高程序的運行效率。
多線程的不安全性
既要保證效率又要保證安全的時代,線程的不安全性問題比較突出。多個線程同時訪問一個共享變量時,就可能達不到想要的預期。
public class SyncDemo {
private static int count = 0;
public static void incr(){
count ++;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(()->{
SyncDemo.incr();
}).start();
}
TimeUnit.SECONDS.sleep(1);
//不等於1000
System.out.println(count);
}
}
鎖的升級
JDK1.6之前synchronized鎖屬於重量級鎖,重量級鎖會掛起等待線程,掛起CPU開銷比較大,所以在JDK1.6後做了優化,引入了偏向鎖、輕量級鎖的概念。注意鎖智能升級不能降級。
鎖的升級順序如下:
無鎖 >> 偏向鎖 >> 輕量級鎖 >> 重量級鎖
假如現在有兩個線程ThreadA / ThreadB
-
只用ThreadA線程去訪問 – 偏向鎖
-
ThreadA和ThreadB兩個線程交替訪問 – 輕量級鎖
-
多個線程同時訪問 – 重量級鎖
偏向鎖和輕量級鎖相當無沒有鎖,爲什麼鎖這兩種鎖沒有鎖呢?
偏向鎖
偏向鎖可以使用JVM參數去關閉,因爲在現實場景中很少會出現這種情況。
偏向鎖在JVM中的實現:
線程ThreadA首次獲得鎖對象的時候,會修改鎖對象中的對象頭,
記錄一下:現在鎖的狀態是偏向鎖和自己的線程ID,待ThreadA再次訪問時,就不用再獲得鎖了,可以直接執行被鎖的代碼塊。
輕量級鎖
絕大多部分情況在線程獲得鎖以後,在非常短的時間內會釋放鎖,線程交替等待時,使用自旋獲得鎖。
自旋會佔用CPU資源,所以在指定的自選次數之後,如果還沒有獲得輕量級鎖,鎖會膨脹成重量級鎖 (BLOCKED 阻塞狀態)
輕量級鎖是怎麼存儲的呢?
ThreadA線程在獲得鎖後,會創建以個線程棧幀存儲鎖的對象頭,然後修改對象頭中存儲該線程棧幀的hash值標識自己。如果ThradB獲得鎖後,也是同樣。
重量級鎖
沒有獲得到鎖的線程會被阻塞,等待CPU再次調度。
爲什麼重量級鎖會比較消耗CPU?
wait、notify、notifyAll對線程的影響
wait 實現線程的阻塞、會釋放當前同步鎖
notify/notifyAll會喚醒線程
ThreadA
public class ThreadA extends Thread{
private Object lock = null;
public ThreadA(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("ThreadA start");
try {
//ThreadA.sleep(222222222);
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadA end");
}
}
}
ThreadB
public class ThreadB extends Thread{
private Object lock = null;
public ThreadB( Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("ThreadB start");
lock.notify();
System.out.println("ThreadB end");
}
}
}
共用同一把鎖
public class WaitNotifyTest {
private static Object lock = new Object();
public static void main(String[] args) {
ThreadA threadA = new ThreadA(lock);
ThreadB threadB = new ThreadB(lock);
threadA.start();
threadB.start();
}
}
輸出結果:
ThreadA start
ThreadB start
ThreadB end
ThreadA end
ThradA獲得鎖 ——> .wait()後阻塞,釋放鎖 ——>ThreadB獲得鎖 ——> 喚醒ThreadA ——> ThreadB釋放鎖 ——> ThreadA重新獲得鎖執行代碼
wait和sleep
wait會釋放鎖資源,並且釋放CPU
sleep不會釋放鎖資源,但是會出讓CPU