Java多線程

開啟線程方式

1. 繼承自Thread類的線程

public class ThreadExer {
    public static void main(String[] args){
        Thread thread = new MyThread();
        thread.setName("sub thread");//設置子線程的名稱
        thread.start();
        for(int i=0;i<20;i++){
            try {
                Thread.sleep(100);
                System.out.println("thread name:"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for(int i=0;i<20;i++){
            try {
                Thread.sleep(100);
                System.out.println("thread name:"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. 實現(xiàn)Runnable接口的線程

public class ThreadExer {
    public static void main(String[] args){
        Thread thread = new Thread(new MyRunnable(),"sub thread(sub thread)");
        thread.start();
        for(int i=0;i<20;i++){
            try {
                Thread.sleep(100);
                System.out.println("thread name:"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            try {
                Thread.sleep(100);
                System.out.println("thread name:"+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

實現(xiàn)Runnable接口和繼承自Thread的區(qū)別

  1. 實現(xiàn)Runnable接口的方法,只要傳入同一個Runnable對象即可實現(xiàn)資源共享
  2. 可以避免java中單繼承的限制

線程狀態(tài)

線程內部狀態(tài)

線程內部定義了線程狀態(tài)的枚舉值如下

public static enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;

    private State() {
    }
}
  • NEW 新建狀態(tài)
    ==Thread thread = new Thread(runnable);== 使用new操作符創(chuàng)建Thread對象則處于NEW狀態(tài)

  • RUNNABLE 可運行狀態(tài)
    ==thead.start()==

  • BLOCKED 阻塞狀態(tài)

  • WAITING 等待狀態(tài)

  • TIMED_WAITING 休眠狀態(tài)

  • TERMINATED 終止狀態(tài)

一般所說的5種狀態(tài)

  1. 新建狀態(tài)(New) :新創(chuàng)建了一個線程對象。
  2. 就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權。
  3. 運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
  4. 阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉到運行狀態(tài)。阻塞的情況分三種:
    1. 等待阻塞:運行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。(wait會釋放持有的鎖)
    2. 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
    3. 其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態(tài)。(注意,sleep是不會釋放持有的鎖)
  5. 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結束生命周期。

線程狀態(tài)轉化

image

等待Blocked,鎖定Blocked都屬于阻塞狀態(tài)。

Thead常用Api

1. sleep()

// Thread.java
public static void sleep(long millis) throws InterruptedException

使當前線程主動讓出CPU,CPU去執(zhí)行其他線程,在sleep指定的時間過后,該線程進入就緒狀態(tài),線程重新?lián)寠Z執(zhí)行權,如果當前線程進入運行狀態(tài)會接著sleep后的代碼繼續(xù)執(zhí)行。如果當前線程進入了同步鎖,sleep方法并不會釋放鎖,即使當前線程使用sleep方法讓出了CPU,但其他被同步鎖擋住了的線程也無法得到執(zhí)行

Thread.sleep(0)的作用是"觸發(fā)操作系統(tǒng)立刻重新進行一次CPU競爭,重新計算優(yōu)先級

2. yield()

// Thread.java
public static native void yield();

使當前線程由"運行狀態(tài)"進入到"就緒狀態(tài)",從而讓其它具有相同優(yōu)先級的等待線程獲取執(zhí)行權;但是,并不能保證在當前線程調用Thead.yield()之后,其它具有相同優(yōu)先級的線程就一定能獲得執(zhí)行權;也有可能是當前線程又進入到“運行狀態(tài)”繼續(xù)運行!

3. join()

// Thread.java
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException

在A線程中調用b.join()方法,會使A線程進入阻塞狀態(tài),直到B結束生命周期或到達了給定時間

4. interrupt相關方法

interrupt相關方法如下

// Thread.java
1. public void interrupt()
3. public native boolean isInterrupted()
2. public static native boolean interrupted()
  1. interrupt()
    當B線程調用wait,sleep,join等方法會使線程進入阻塞狀態(tài),在A線程中調用b.interrupt()方法就會打斷B線程阻塞狀態(tài),wait,sleep,join等方法就會拋出InterruptedException。一個線程內部存在interrupt flag,默認false,當被打斷時interrupt會被置為true,當捕獲InterruptedException異常時,該interrupt又會被置false。
  2. isInterrupted()
    b.isInterrupted()會判斷B線程是否被中斷,僅僅通過interrupt flag判斷,并不會影響interrupt的改變
  3. interrupted()
    調用Thread.interrupted()方法會首先返回當前線程的interrupt的狀態(tài),如果interrupt為true,則會置interrupt為false

5. wait/notify/notifyAll()

Object類中定義了wait(),notify(),notifyAll()方法

1.  public final native void wait() throws InterruptedException;
    public final void wait(long millis) throws InterruptedException
2.  public final native void notify();
3.  public final native void notifyAll();
  1. wait() o.wait()鎖對象o調用wait方法使當前線程進入阻塞狀態(tài),jvm會使調用者所屬線程進入waitset,并立刻釋放鎖對象,直到通過調用該鎖對象o的notify()或notifyAll()方法將其喚醒,或者阻塞時間到指定時間時自動喚醒
  2. notify() 通過鎖對象調用notify后會將waitset中的一個線程彈出,具體彈出哪個取決于jvm,彈出的線程將可以重新獲得鎖
  3. notifyAll() 通過鎖對象調用notifyAll后會彈出waitset中的所有線程,這些線程會爭奪鎖,得到鎖的線程才可以繼續(xù)執(zhí)行。

wait,notify,notifyAll方法必須在同步方法中使用,且調用者就是鎖對象,對于同步方法來說就是this,對于同步代碼塊來說就是指定的鎖對象。

有下面兩個問題需要注意

1. 首先為什么wait、notify和notifyAll方法要和synchronized關鍵字一起使用?

因為wait方法是使一個線程進入等待狀態(tài),并且釋放其所持有的鎖對象,notify方法是通知等待該鎖對象的線程重新獲得鎖對象,然而如果沒有獲得鎖對象,wait方法和notify方法都是沒有意義的,因此必須先獲得鎖對象再對鎖對象進行進一步操作于是才要把wait方法和notify方法寫到同步方法和同步代碼塊中了。

由此可知,wait和notify、notifyAll方法是由確定的對象即鎖對象來調用的,鎖對象就像一個傳話的人,他對某個線程說停下來等待,然后對另一個線程說你可以執(zhí)行了(實質上是被捕獲了),這一過程是線程通信。sleep方法是讓某個線程暫停運行一段時間,其控制范圍是由當前線程決定,運行的主動權是由當前線程來控制(擁有CPU的執(zhí)行權)。

2. 鎖(monitor)池和等待池(waitset)的區(qū)別?

  • 鎖池 假設線程A已經擁有了某個對象(注意:不是類)的鎖,而其它的線程想要調用這個對象的某個synchronized方法(或者synchronized塊),由于這些線程在進入對象的synchronized方法之前必須先獲得該對象的鎖的擁有權,但是該對象的鎖目前正被線程A擁有,所以這些線程就進入了該對象的鎖池中。簡單點說,如果有個線程已拿到某個對象鎖,其他線程要執(zhí)行含有該鎖的synchronized方法時就會進入鎖池
  • 等待池 假設當前A線程持有鎖對象后,調用了這個鎖對象的 wait() 方法,則 A線程就會釋放該對象的鎖(因為 wait() 方法必須出現(xiàn)在 synchronized 中,所以在執(zhí)行 wait() 方法之前 A 線程就已經擁有了該對象的鎖),同時線程 A會進入該鎖對象的等待池中。如果有其它線程持有上述鎖對象后,并調用了該鎖對象的 notifyAll() 方法,那么處于該鎖對象的等待池中的線程就會全部進入該鎖對象的鎖池中,從新爭奪鎖的擁有權。如果另外的一個線程調用了相同對象的 notify() 方法,那么僅僅有一個處于該對象的等待池中的線程(隨機)會進入該對象的鎖池。

線程調度

線程優(yōu)先級

Java線程的優(yōu)先級用整數(shù)表示,取值范圍是1~10,Thread類有以下三個靜態(tài)常量

public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;

Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優(yōu)先級。

每個線程都有默認的優(yōu)先級。主線程的默認優(yōu)先級為Thread.NORM_PRIORITY。
線程的優(yōu)先級有繼承關系,比如A線程中創(chuàng)建了B線程,那么B將和A具有相同的優(yōu)先級。

JVM提供了10個線程優(yōu)先級,但與常見的操作系統(tǒng)都不能很好的映射。如果希望程序能移植到各個操作系統(tǒng)中,應該僅僅使用Thread類有以下三個靜態(tài)常量作為優(yōu)先級,這樣能保證同樣的優(yōu)先級采用了同樣的調度方式。

如果CPU比較忙,設置優(yōu)先級可能會獲得更多的CPU時間,但如果CPU比較空閑,優(yōu)先級幾乎不會起任何作用。所以不要指望依賴優(yōu)先級來綁定某些特定業(yè)務,實際一般不會設置優(yōu)先級,采用默認優(yōu)先級為5.

線程的停止

  1. ~thread.stop()
    stop方法已被廢棄,原因是什么?
  2. thread.setDaemon(true);
public class StopThreadExer {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        // 調用該方法設置守護線程
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

輸出結果

Thread-00
main0
main1
main2
main3
main4

在不調用setDaemon情況下,子線程會輸出5次,上述輸出可見主線程停止了守護線程也停止了,即使是在阻塞的情況下。守護線程是為其他線程服務,當所有的非守護線程結束時, 程序也就終止了,同時會殺死進程中的所有守護線程,所以不要在守護線程中去訪問文件等資源

  1. 標志位(使用最多)
    可以根據(jù)業(yè)務邏輯決定什么時候結束子線程
public class StopThreadExer {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
        MyRunnable.running = false;// 使標志量為false,則子線程不會繼續(xù)執(zhí)行下一次循環(huán),但有有可能正在延時,故需要調用interrupt()方法使打斷阻塞的線程
        thread.interrupt();
    }
}

class MyRunnable implements Runnable {
    public static boolean running = true;
    @Override
    public void run() {
        for (int i = 0; i < 5&&running; i++) {
            System.out.println(Thread.currentThread().getName() + i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意 MyRunnable.running = false;thread.interrupt();

1.線程生命周期正常結束

任務耗時短時或者時間可控放任正常結束即可

2. 通過中斷信號interrupt關閉線程

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            super.run();
            System.out.println("I will start work");
            while (!isInterrupted()) {
                // working
            }
            System.out.println("I will be exiting");
        }
    };
    thread.start();
    TimeUnit.SECONDS.sleep(5);
    // 1.
    thread.interrupt();
}

注釋1處調用interrupt后,thread線程的interrupt標識就會返回true,isInterrupted()方法就會返回true,則while循環(huán)條件為false,接著執(zhí)行下一句輸出生命周期結束,線程結束。

Thread thread = new Thread() {
    @Override
    public void run() {
        super.run();
        System.out.println("I will start work");
        while (true) {
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                // 1.
                break;
            }
        }
        System.out.println("I will be exiting");
    }
};
thread.start();
TimeUnit.SECONDS.sleep(5);
// 2.
thread.interrupt();

注釋2處調用interrupt后,thread線程注釋1處會捕獲InterruptedException異常,捕獲到該異常時退出循環(huán)即可,進而執(zhí)行下個輸出語句,最后線程停止。

3. volatile 變量控制開關

由于interrupt標識很有可能被擦除,故加入flag來控制,注意需要使用volatile關鍵字修飾,保證每個線程可以讀取到主內存中最新的值,否則可能會引起線程的無線循環(huán)

static class MyThread extends Thread {
    private volatile boolean closed;

    @Override
    public void run() {
        super.run();
        System.out.println("I will start work");
        while (!closed && !isInterrupted()) {
            // working
        }
        System.out.println("I will be exiting");
    }

    public void close() {
        closed = true;
        this.interrupt();
    }
}

public static void main(String[] args) throws InterruptedException {
    MyThread thread = new MyThread();
    thread.start();
    TimeUnit.SECONDS.sleep(5);
    thread.close();
}

同步

同步是為了解決多線程共同訪問數(shù)據(jù)導致數(shù)據(jù)出現(xiàn)問題,它使多線程可以實現(xiàn)對一段代碼互斥訪問,也就是在同步代碼中只會存在一個線程執(zhí)行

  • 同步方法
private int count;
public synchronized void count(){
    count++;
}

使用synchronized修飾符修飾成員方法,此時鎖對象默認為當前對象

private static int count;
public static synchronized void count(){
    count++;
}

修改在靜態(tài)方法上,鎖對象默認為當前類對象(類名.class)

  • 同步代碼塊
synchronized (this){
    count++;
}

括號中可以是任意對象。

線程間的協(xié)作

1. Object類中的成員方法

  • wait()
    使當前持有該鎖的線程釋放鎖,并變?yōu)?strong>阻塞狀態(tài),然后加入鎖對象的等待隊列(等待池)中
  • notify()
    由當前持有同步鎖的線程調用,以喚醒鎖對象的等待隊列中的一個線程,使其變?yōu)榫途w狀態(tài)。直到當前線程釋放鎖,被喚醒的線程才可能被執(zhí)行,被喚醒后且得到了鎖的線程,才會從wait方法的下一句繼續(xù)執(zhí)行。
  • notifyAll()
    由當前持有同步鎖的線程調用,以喚醒鎖對象的等待隊列中的所有線程,被喚醒的線程將競爭鎖

2. 生產者和消費者

public class ThreadExer {
    public static void main(String[] args) {
        List list = new ArrayList();
        Runnable producerRunnable = new ProducerRunnable(list);
        Runnable consumerRunnable = new ConsumerRunnable(list);

        Thread producer = new Thread(producerRunnable, "producer1");
        Thread producer1 = new Thread(producerRunnable, "producer2");
        Thread producer2 = new Thread(producerRunnable, "producer3");

        Thread consumer = new Thread(consumerRunnable, "consumer1");
        Thread consumer1 = new Thread(consumerRunnable, "consumer2");
        Thread consumer2 = new Thread(consumerRunnable, "consumer3");
        Thread consumer3 = new Thread(consumerRunnable, "consumer4");
        producer.start();
        producer1.start();
        producer2.start();

        consumer.start();
        consumer1.start();
        consumer2.start();
        consumer3.start();
    }
}

class ProducerRunnable implements Runnable {
    private static final int MAX_CONTAINER_SIZE = 10;

    List container;
    String produceProductName;
    int count;

    public ProducerRunnable(List container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            synchronized (container) {
                // 由于生產者被喚醒后會接著wait()后的一句代碼進行執(zhí)行,當一個被喚醒的生產者搶奪鎖后生產一個后釋放鎖,當?shù)诙€生產者擁有鎖后也會接著wait方法后執(zhí)行,故無法判斷容器已滿,故采用循環(huán)判斷。
                while (container.size() == MAX_CONTAINER_SIZE) {
                    System.out.println("容器已滿,等待消費");
                    try {
                        container.wait();// 讓當前生產者線程進入該鎖對象的等待池,并釋放該鎖,
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                produceProductName = (count++) + "號產品";
                System.out.println(Thread.currentThread().getName()+"生產 " + produceProductName);
                container.add(produceProductName);
                container.notifyAll(); // 會喚醒等待池中的所有線程,代碼走到此處說明容器沒滿,意味著沒有生產者處于等待池,則意思就是喚醒所有消費者線程去競爭鎖
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ConsumerRunnable implements Runnable {
    private static final int MIN_CONTAINER_SIZE = 0;

    List container;
    String consumeProductName;

    public ConsumerRunnable(List container) {
        this.container = container;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (container) {
                while (container.size() == MIN_CONTAINER_SIZE) {
                    System.out.println("容器為空,等待生產");
                    try {
                        container.wait(); // 容器為空,消費者加入等待池
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                consumeProductName = (String) container.remove(0);
                System.out.println(Thread.currentThread().getName() + " 消費" + consumeProductName);
                container.notifyAll(); // 代碼走到此處說明所有等待池中的消費者都被喚醒,故此處會喚醒處于等待池中的所有生產者
            }
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

下面log為截取了一段輸出

producer1生產 0號產品
consumer2 消費0號產品
容器為空,等待生產
容器為空,等待生產
容器為空,等待生產
producer3生產 1號產品
producer2生產 2號產品
consumer1 消費1號產品
consumer4 消費2號產品
容器為空,等待生產
producer1生產 3號產品
consumer3 消費3號產品
producer2生產 4號產品
producer3生產 5號產品
producer3生產 6號產品
producer2生產 7號產品
producer1生產 8號產品

這個示例3個生產者總共會生產60個產品,但容器里最多只能放入10個產品,放滿時生產者等待,當容器產品被消費消費完時則消費者等待。使用wait和notify實現(xiàn)了生產者和消費者的協(xié)作。

死鎖

實際寫代碼要避免死鎖

public class DeadLock {
    public static Object obj1 = new Object();
    public static Object obj2 = new Object();

    public static void main(String[] args) {
        Runnable aRunnable = new ARunnable();
        Runnable bRunnable = new BRunnable();
        Thread thread1 = new Thread(aRunnable);
        Thread thread2 = new Thread(bRunnable);
        thread1.start();
        thread2.start();
    }

}

class ARunnable implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLock.obj1) {
            System.out.println("A Runnable obj1 lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (DeadLock.obj2) {
                System.out.println("A Runnable obj2 lock");
            }
        }
    }
}

class BRunnable implements Runnable {

    @Override
    public void run() {
        synchronized (DeadLock.obj2) {
            System.out.println("B Runnable obj2 lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (DeadLock.obj1) {
                System.out.println("B Runnable obj1 lock");
            }
        }
    }
}

輸出

A Runnable obj1 lock
B Runnable obj2 lock

A線程獲得到obj1鎖后睡眠1s不會釋放鎖,1s沒有到達時,B線程此時獲得到obj2鎖,接著A線程去嘗試獲得B線程已經持有的obj2鎖,而B線程又在等待A線程中的obj1鎖,從而導致了死鎖。

死鎖檢查分析

  1. jconsole 運行工具點擊detect lock
  2. jstack [pid] 即可在stack信息中查看對應進程的死鎖情況

jps 命令查看java程序pid。截取主要信息如下

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at com.nan.mylibrary.BRunnable.run(DeadLock.java:48)
    - waiting to lock <0x000000076d674558> (a java.lang.Object)
    - locked <0x000000076d674568> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0":
    at com.nan.mylibrary.ARunnable.run(DeadLock.java:30)
    - waiting to lock <0x000000076d674568> (a java.lang.Object)
    - locked <0x000000076d674558> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

知識點

  1. 守護線程
    hread.setDaemon(true);
    通過設置該屬性則線程為守護線程,守護線程是為其他線程服務的,如果其他線程停止運行了,虛擬機中只剩守護線程,則虛擬機回退出
  2. 線程名稱
    通過setName可以設置線程名稱,不同的線程可以有相同的線程名稱,不能使用線程名稱來區(qū)分線程
  3. 線程ID
    每個線程創(chuàng)建時則會分配線程ID,通過getId()即可拿到線程ID,無法修改==系統(tǒng)通過ID來區(qū)分不同的線程==
  4. Java中程序運行至少會啟動2個線程。一個是main線程,一個是垃圾收集線程

特性

1 使用同一個runnable接口創(chuàng)建的線程可以變量資源的共享

2 線程在創(chuàng)建后就被分配內存空間,該空間也在JVM中堆內存中,其私有數(shù)據(jù)已被初始化

3 異常無法跨線程拋出或捕獲

4 調用yield()方法后調度器會將該線程放入等待隊列的末尾

5 同步方法鎖對象為this,同步塊鎖對象為傳入的對象,對于加鎖,釋放鎖是有JVM自動完成的

6 當使用不是同一個Runnable接口或繼承Thread創(chuàng)建的線程所加的同步方法,持有的鎖不是同一把鎖,因為對象發(fā)生了變化

如果對于繼承Thread類想要互斥訪問同一個變量,則需使變量變?yōu)閟tatic,并且同步鎖需加在static方法上,此時鎖對象為Class對象,從而具有同一把鎖,實現(xiàn)了互斥訪問

Java內存模型

java內存模型(JMM):

  • 所有的變量都存儲在主內存中

  • 每個線程都有自己獨立的工作內存,里面保存了該線程使用到的變量的副本(主內存中該變量的一份拷貝)

JMM規(guī)定

1)線程對共享變量的所有操作都必須在自己的工作內存中進行, 不能直接從主內存中讀寫

2)不同線程之間無法直接訪問其它線程工作內存中的變量,線程間變量值的傳遞是需要通過主內存來完成的。

synchronized:具有可見性,有序性,也具有原子性

JMM關于synchronized的兩條規(guī)定

  1. 線程解鎖前,必須把共享變量的最新值刷新到主內存中
  2. 線程加鎖時,將清空工作內存中的共享變量的值,從而使用共享變量時要從主內存中重新讀取最新的變量的值(加鎖和解鎖必須是同一把鎖)

線程執(zhí)行互斥代碼的過程:

(1)獲得互斥鎖
(2)清空工作內存
(3)從主內存拷貝變量最新的值給工作內存中的副本
(4)執(zhí)行代碼
(5)將修改后的共享變量的值刷新到主內存中
(6)釋放互斥鎖

volatile關鍵字

  • 具有可見性、有序性,不具備原子性。其不會進行鎖操作,所以性能更好。

注意,volatile不具備原子性,這是volatile與java中的synchronized、java.util.concurrent.locks.Lock最大的功能差異

下面來分別看下可見性、有序性、原子性:

  • 原子性:如果你了解事務,那這個概念應該好理解。原子性通常指多個操作不存在只執(zhí)行一部分的情況,如果全部執(zhí)行完成那沒毛病,如果只執(zhí)行了一部分,那對不起,你得撤銷(即事務中的回滾)已經執(zhí)行的部分。

  • 可見性:當多個線程訪問同一個變量x時,線程1修改了變量x的值,線程1、線程2...線程n能夠立即讀取到線程1修改后的值。

    • 寫操作:就是修改線程的工作內存中副本的值,然后刷新到主內存中,
    • 讀操作:從主內存中讀取值到工作內存中的副本,然后再取副本中的值
  • 有序性:即程序執(zhí)行時按照代碼書寫的先后順序執(zhí)行。在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性。(本文不對指令重排作介紹,但不代表它不重要,它是理解JAVA并發(fā)原理時非常重要的一個概念)。

  • 不具有原子性
    number++不具有原子性:eg:具有A,B線程,number = 5,然后A,B線程分別對其進行加一操作

(1)A線程執(zhí)行number++讀到值時阻塞,B線程執(zhí)行
(2)B線程進行加1操作后為6,并會把修改的值刷新到主內存中,A線程執(zhí)行
(3)A線程繼續(xù)進行加1操作,修改后的值為6,從而產生問題

線程問題整理

  1. 如果調用Thread的start()方法兩次則會報如下異常
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:705)
    at com.example.ThreadExer.main(ThreadExer.java:24)
thread name:Thread-0
thread name:Thread-0
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 進程和線程 進程 所有運行中的任務通常對應一個進程,當一個程序進入內存運行時,即變成一個進程.進程是處于運行過程中...
    勝浩_ae28閱讀 5,267評論 0 23
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,115評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,602評論 1 15
  • 該文章轉自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,473評論 3 87
  • 最近哈里王子結婚了,刷屏搶鏡的竟然不是王室成員,而是這對強強聯(lián)手的高顏值夫妻。 這是多少父母盼望自己的子女能有如此...
    LAYLA景一閱讀 498評論 0 0

友情鏈接更多精彩內容