java并發(fā)

并發(fā)核心理論

  • 1.共享性: 數(shù)據(jù)共享是線程安全問題的主要問題之一

  • 2.互斥性: 資源互斥指同一時間內(nèi)值允許一個訪問者訪問.通常情況修改數(shù)據(jù)是互斥性,讀數(shù)據(jù)不要求.因此我們有共享鎖和互斥鎖.也叫讀鎖和寫鎖.

  • 3.原子性:指操作是一個獨立,不可分割的整體.操作不會中斷,數(shù)據(jù)不會執(zhí)行一半的時候被其他操作修改.例如一條指令就是最基本的原子.但是 i++,就分了好幾次操作:

    讀?。椋剑敝怠。?gt;?。玻椋剑?1 --->3.存儲i=2

  • 4.可見性:


    image.png

    每個線程都有自己的工作內(nèi)存,對于共享變量,先拿到變量的副本,對副本進行操作,在某一個時間在把副本的值同步到共享變量.這樣導致,有可能線程1修改了共享變量的值,某一時間線程2可能拿不到最新的值.

  • 5.有序性:為了提高性能,編譯器和處理器可能會對指令進行重排.

synchronized及其實現(xiàn)原理

synchronized 作用:

  • 1.確保線程互斥的訪問同步代碼
  • 2.保證共享變量能及時可見
  • 3.解決重排序問題

synchronize 的一般用法,對象鎖,類鎖.(synchronized Object,synchronized 方法)
對class文件反編譯后的匯編語言如下:


image.png

image.png

說明synchronized 是在對象前面加了 monitor .
Monitorenter : 對象都享有一個monitor
1.線程一旦進入monitor,monitor值1,線程為monitor擁有者.
2.其他線程到了這里,會進行阻塞,直到monitor的值=0.
Monitorexit:monitor 變回0.說明其他線程可以擁有.

對方法加synchronized

image.png

image.png

Synchronized底層優(yōu)化(偏向鎖、輕量級鎖)
優(yōu)點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級的差距。 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 適用于只有一個線程訪問同步塊場景。
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度。 如果始終得不到鎖競爭的線程使用自旋會消耗CPU。 追求響應時間。同步塊執(zhí)行速度非???。
重量級鎖 線程競爭不使用自旋,不會消耗CPU。 線程阻塞,響應時間緩慢。 追求吞吐量。同步塊執(zhí)行時間較長。

鎖的狀態(tài): 無鎖狀態(tài),偏向鎖 ,輕量級鎖,重量級鎖
隨著鎖的競爭,鎖的狀態(tài)可以升級,但是是單向的.從低到高,不會出現(xiàn)鎖的降級.
偏向鎖<輕量級鎖<重量級鎖


image.png

輕量級鎖:本意使用操作系統(tǒng)互斥量解決傳統(tǒng)的重量級鎖的性能問題.
使用場景:線程交替執(zhí)行同步塊.如果同一時刻訪問同一鎖的情況,輕量級鎖會升級重量級鎖.
偏向鎖:用于一個線程執(zhí)行同步塊是提高性能.一旦出現(xiàn)多線程競爭,升級為輕量級鎖.
總結(jié) :JDk中采用輕量級鎖和偏向鎖等對Synchronized的優(yōu)化,但是這兩種鎖也不是完全沒缺點的,比如競爭比較激烈的時候,不但無法提升效率,反而會降低效率,因為多了一個鎖升級的過程,這個時候就需要通過-XX:-UseBiasedLocking來禁用偏向鎖。下面是這幾種鎖的對比:

優(yōu)點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級的差距。 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 適用于只有一個線程訪問同步塊場景。
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度。 如果始終得不到鎖競爭的線程使用自旋會消耗CPU。 追求響應時間。同步塊執(zhí)行速度非??臁?/td>
重量級鎖 線程競爭不使用自旋,不會消耗CPU。 線程阻塞,響應時間緩慢。 追求吞吐量。同步塊執(zhí)行時間較長。

Java并發(fā)編程:線程間的協(xié)作(wait/notify/sleep/yield/join)

線程有5中狀態(tài):
新建(New)-->準備狀態(tài)(Runnable)-->運行狀態(tài)(Running)-->阻塞(Blocking)-->死亡(Dead)

new :創(chuàng)建 new Thread();
Runnable:調(diào)用start(),進入就緒狀態(tài),等待cpu分配資源,有系統(tǒng)運行時線程來調(diào)度
Running:開始執(zhí)行Run方法
Blocking: 阻塞
Dead
Wait/notify/notifyAll()
wait() 當前線程掛起,直到有其他線程調(diào)用notify(), notifyAll().
wait(long timeout) 當前線程掛起,直到有其他線程調(diào)用notify(), notifyAll();或者等到timeout
wait(long timeout,int nanos)
notify() 喚醒指定線程 , notifyAll() 喚醒所有的線程
注意:wait 必須在synchronized 塊之內(nèi).
Thread.sleep(long sleeptime)
當前線程暫停指定的時間(毫秒),而更深層次的區(qū)別在于sleep方法只是暫時讓出CPU的執(zhí)行權,并不釋放鎖.而wait方法則需要釋放鎖,(意味其他線程可以拿到鎖,進行操作)

package jni.test.leon.javatest;
/**
 * Created by leon on 17-12-11.
 */
public class SleepTest {
    public synchronized void sleepMethod() {
        System.out.println("Sleep start-----");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Sleep end-----");
    }
    public  void waitMethod() {
        System.out.println("Wait start-----");
        synchronized (this) {
            try {
                wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Wait end-----");
    }
    public static void main(String[] args) {
        final SleepTest test1 = new SleepTest();
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test1.sleepMethod();
                }
            }).start();
        }
        try {
            Thread.sleep(10000);//暫停十秒,等上面程序執(zhí)行完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----分割線-----");
        final SleepTest test2 = new SleepTest();
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.waitMethod();
                }
            }).start();
        }
    }
}
//output-----
Connected to the target VM, address: '127.0.0.1:38691', transport: 'socket'
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
-----分割線-----
Wait start-----
Wait start-----
Wait start-----
Wait end-----
Disconnected from the target VM, address: '127.0.0.1:38691', transport: 'socket'
Wait end-----
Wait end-----

Process finished with exit code 0

Thread.yeild()
當前線程暫停,讓其他線程有機會執(zhí)行.(用的場景比較少,主要是調(diào)試)

package jni.test.leon.javatest;
/**
 * Created by leon on 17-12-11.
 */
public class YieldTest implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.yield();
        }
    }
    public static void main(String[] args) {
        YieldTest runn = new YieldTest();
        Thread t1 = new Thread(runn, "FirstThread");
        Thread t2 = new Thread(runn, "SecondThread");
        t1.start();
        t2.start();
    }
}
//--output
FirstThread: 0
FirstThread: 1
SecondThread: 0
FirstThread: 2
SecondThread: 1
FirstThread: 3
SecondThread: 2
FirstThread: 4
SecondThread: 3
SecondThread: 4

Thread.join()/Thread.join(long waittime)/Thread.join(long waittime,int nano)
作用:父線程等待子線程執(zhí)行完之后在執(zhí)行,可以達到異步子線程,最后的同步.

package jni.test.leon.javatest;
/**
 * Created by leon on 17-12-11.
 */
public class JoinTest implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
            System.out.println(Thread.currentThread().getName() + " start-----");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " end------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Thread test = null;
        for (int i = 0; i < 5; i++) {
            test = new Thread(new JoinTest());
            test.start();
        }
        //這里是阻塞
        try {
            test.join(); //調(diào)用join方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Finished~~~");
    }
}
//output------
Thread-0 start-----
Thread-1 start-----
Thread-2 start-----
Thread-3 start-----
Thread-4 start-----
Thread-0 end------
Thread-1 end------
Thread-2 end------
Thread-3 end------
Thread-4 end------
Finished~~~

總結(jié):
因為wait() /notify () 是對象monitor,所以當wait的時候就monitorEnter,必須等待有monitorExit 才能釋放對象的擁有權.所以,一旦wait 了,其他的線程是無法獲得擁有權,也就不能進入.

而sleep, yield這些是Thread級別的方法,只是讓出cpu執(zhí)行權join,調(diào)用的是wait 方法,所以是會進行對象級別的monitor.

volatile的使用及其原理

我們知道 可見性,有序性,原子性 的問題.synchronized 就是用來解決這些問題的,但是synchronized是比較重量級,volatile是一個輕量級的解決有序性,可見性,原子性的方案.
原理:
1.對有序性:
在解釋這個問題前,我們先來了解一下Java中的happen-before規(guī)則,JSR 133中對Happen-before的定義如下:

Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.

通俗一點說就是如果a happen-before b,則a所做的任何操作對b是可見的。(這一點大家務必記住,因為happen-before這個詞容易被誤解為是時間的前后)。我們再來看看JSR 133中定義了哪些happen-before規(guī)則:

  • Each action in a thread happens before every subsequent action in that thread.
  • An unlock on a monitor happens before every subsequent lock on that monitor.
  • A write to a volatile field happens before every subsequent read of that volatile.
  • A call to start() on a thread happens before any actions in the started thread.
  • All actions in a thread happen before any other thread successfully returns from a join() on that thread.
  • If an action a happens before an action b, and b happens before an action c, then a happens before c.

翻譯過來為:

  • 同一個線程中的,前面的操作 happen-before 后續(xù)的操作。(即單線程內(nèi)按代碼順序執(zhí)行。但是,在不影響在單線程環(huán)境執(zhí)行結(jié)果的前提下,編譯器和處理器可以進行重排序,這是合法的。換句話說,這一是規(guī)則無法保證編譯重排和指令重排)。
  • 監(jiān)視器上的解鎖操作 happen-before 其后續(xù)的加鎖操作.(Synchronized 規(guī)則)
  • 對volatile變量的寫操作 happen-before 后續(xù)的讀操作。(volatile 規(guī)則)
  • 線程的start() 方法 happen-before 該線程所有的后續(xù)操作。(線程啟動規(guī)則)
  • 線程所有的操作 happen-before 其他線程在該線程上調(diào)用 join 返回成功后的操作。
  • 如果 a happen-before b,b happen-before c,則a happen-before c(傳遞性)。
    這里我們主要看下第三條:volatile變量的保證有序性的規(guī)則<<Java并發(fā)編程:核心理論>>

2.可見性
使用 Volatile 關鍵字
1.修改時會強制修改主內(nèi)存的數(shù)據(jù)
2.修改變量后導致其他線程工作中內(nèi)存的值失效,所以需要重新讀取主內(nèi)存的數(shù)據(jù)
volatile 的使用場景比較有限:
1.該變量寫操作不依賴當前值
2.該變量沒有包含在具有其他變量的不變式中(這個變量只有原子性操作,簡單說來就是 進行賦值)

常用:
1.狀態(tài)標記量

A:
volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}

B:
volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            
 
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2.double check

class Singleton{
    private volatile static Singleton instance = null;
 
    private Singleton() {
 
    }
 
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容