volatile synchroized
java支援多個多個線程同時通路一個對象或者對象的成員變量,由于每個線程都可以擁有這個變量的拷貝,這樣的目的在于加速程式的執行。是以,一個線程看到的變量的并不一定就是最新的。
volatile可以修飾字段(成員變量),就是告知程式任何對該變量的通路都需要從共享記憶體中擷取,對他的改變必須同步重新整理到共享記憶體(主記憶體),保證所有線程對這個變量的可見性。
synchorized可以修飾方法或者修飾代碼塊,主要就是確定多個線程在同一時刻,隻能有一個線程處于方法或者代碼塊中,保證線程對變量通路的可見性課排他性。
public class Synchorized {
public static void main(String[] args) {
synchronized (Synchorized.class) {
}
m();
}
public static synchronized void m() {
}
}
```運作javap -v指令會發現
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class ja
va并發程式設計的藝術/one/Synchorized
2: dup
3: astore_1
4: monitorenter
5: aload_1
6: monitorexit
7: goto 15
10: astore_2
11: aload_1
12: monitorexit
13: aload_2
14: athrow
15: invokestatic #3 // Method m
😦)V
18: return
public static synchronized void m();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 13: 0
上面的class資訊中,
同步塊的實作使用了monitorenter和monitorexit指令。
而同步方法是依靠方法修飾符上的ACC_SYNCHRONIZED來完成的。
上面兩個方式的的本質都是:對一個對象的螢幕(monitor)的擷取。則這個過程是排他的。同一時刻,隻能有一個線程擷取到這個由synchorized所保護的對象的螢幕。
任意一個對象都擁有自己的螢幕,當這個對象由同步塊或者同步方法調用時,必須先擷取該對象的螢幕才能進入。沒有擷取到螢幕的線程就會阻塞到同步塊或者同步方法的入口處。進入BLOCKED狀态。

上圖中,如果Object的前驅(擷取到鎖的線程)釋放了鎖。這個釋放操作就會喚醒阻塞在同步隊列彙總的線程,使其重新嘗試對螢幕的擷取。
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "wait thread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(),"notify thread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
//加鎖,擁有lock的monitor
synchronized (lock) {
while (flag) {
try {
System.out.println(Thread.currentThread() + "flag is true,wait___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
//條件滿足時,完成工作
System.out.println(Thread.currentThread() + "flag is false,running___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
synchronized (lock) {
//擷取鎖,然後通知,通知的時不會釋放鎖
//直到目前線程釋放了鎖,waitThread才能從wait中傳回
System.out.println(Thread.currentThread() + "hold lock。notify___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
//再次加鎖
synchronized (lock) {
System.out.println(Thread.currentThread() + "hold lock again。notify___" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
輸出:
Thread[wait thread,5,main]flag is true,wait___00:42:28
Thread[notify thread,5,main]hold lock。notify___00:42:29
Thread[notify thread,5,main]hold lock again。notify___00:42:34
Thread[wait thread,5,main]flag is false,running___00:42:39
- 調用wait()、notify()、notifyAll()時,需要先對調用對象加鎖
- 調用wait()後,線程狀态由RUNNING 變為WAITING,并且将目前線程防止到對象的等待隊列。
- notify()或者notifyAll()調用後,等待線程依舊不會從wait()傳回,需要調用notify()或者notifyAll()的線程釋放鎖後,等待線程才有機會從wait()傳回。
- notify()方法将等待隊列總的一個等待線程從等待隊列移到同步隊列中,而notifyAll()方法則是将的等待隊列中所有線程全部移到同步隊列,狀态由WAITING變為BLOCKED
- 從wait()方法傳回的前提是獲得了調用對象的鎖。
- 上圖中,waitThread先擷取了對象的鎖,然後調用對象的wait方法,進而放棄了鎖并進入到等待隊列,進入等待狀态。由于waitThead釋放了鎖,NotifyThread随後便擷取了對象的鎖,并調用了對象的notify()方法,将waitThread 從等待隊列中移到同步隊列中,此時,waitThread的狀态變為阻塞狀态,NotifyThread釋放了鎖之後,waitThread再次擷取鎖并從wait()方法中傳回。
線程間通信-java并發程式設計的藝術volatile synchroized
等待/通知的範式
等待方(消費者):
- 擷取對象的鎖
- 如果條件不滿足,調用對象的wait()方法,被通知後仍要檢查條件
- 條件滿足,執行代碼
synchronized (對象){
while(條件不滿足)
{
對象.wait();
}
dothings();
}
通知方(生産者)
- 擷取對象的鎖
- 改變條件
- 通知所有等待在對象上的線程
synchronized (對象){
改變條件;
對象.notifyAll();
}
Thread.join()的使用
thread.join():目前線程A等待線程終止後才從thread.join()傳回。
join(long millis):在特定時間裡沒有終止,就從逾時方法中傳回
public class Join {
public static void main(String[] args) {
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
//每個線程擁有前一個線程的引用,需要等前一個線程終止,才能從等待中傳回
Thread thread = new Thread(new Demino(previous), String.valueOf(i));
thread.start();
previous = thread;
}
}
static class Demino implements Runnable {
private Thread thread;
public Demino(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "終止了");
}
}
}
0終止了
1終止了
2終止了
3終止了
4終止了
5終止了
6終止了
7終止了
8終止了
9終止了
可以看出:
每個線程的終止的前提就是前驅線程的終止,每個線程等待前驅線程終止後,才從join方法傳回。
當線程終止時,會調用線程自身的notifyAll()方法,會通知所有等待在該線程對象上的線程。