併發編程上(基礎篇)

進程與線程

進程:系統資源調度與分配的基本單位,一個進程至少有一個線程,進程裏面的線程共享進程資源(網絡、磁盤空間、內存)

線程:cpu分配的基本單位(CPU資源共享)

線程共享區和線程獨享區

 

線程的生命週期

新建狀態(new):當線程對象創建後,即新建狀態;

就緒狀態(Runnable):當線程調用start()方法時,線程進入就緒狀態,等待CPU時間片的分配;

運行狀態(Running):當就緒狀態的線程獲取到CPU分配的時間片時,此刻線程進入運行狀態;

阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,

                                     直到其進入到就緒狀態,纔有機會再次被CPU調用以進入到運行狀態;

阻塞的分類

① 同步阻塞:當前線程執行時候需要去獲取鎖的時候(鎖可能被其他線程佔用未被釋放),此刻當前線程會

                        進入阻塞狀態   (synchronized);

② 等待阻塞:當前線程調用wait()方法時,會進入等待阻塞狀態,直到當前線程被通知纔會繼續執行,一般和

                        notify()和notifyAll()方法聯合使用;

③ 其他阻塞:當前線程調用join()會sleep()會進入阻塞狀態,當sleep()狀態超時、join()等待線程終止或者超時、

                       或者I/O處理完畢時,線程重新轉入就緒狀態;

死亡狀態(Dead):當前線程執行完成或者異常退出,那麼當前線程生命週期結束。

線程創建的幾種方式

方式1:通過繼承Thread創建線程,重寫run()方法實現業務

public class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("嗨!我是通過繼承Thread類創建的線程"+this.getName()+",麼麼噠!!!");
    }

    public static void main(String[] args) {
        //方式1運行
        ThreadDemo threadDemo = new ThreadDemo();
        Thread thread = new Thread(threadDemo);
        thread.start();
        //方式2運行
        new ThreadDemo().start();
    }
}

嗨!我是通過繼承Thread類創建的線程Thread-2,麼麼噠!!!
嗨!我是通過繼承Thread類創建的線程Thread-0,麼麼噠!!!

方式2:實現Runnable接口創建線程類,重寫run()實現業務

public class ThreadDemo1 implements Runnable {

    private  static volatile int money = 1;

    /**
     * @see Thread#run()
     */
    @Override
    public void run() {
        System.out.println("嗨,我是通過實現Runnable接口創建的線程"+Thread.currentThread().getName()+"!!!麼麼噠");
    }


    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new ThreadDemo1());
        thread.start();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1 修改成員屬性---> " + ++money);
            }
        });
        thread1.start();

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread2 修改成員屬性---> " + ++money);
            }
        });
        thread2.start();
    }
}

嗨,我是通過實現Runnable接口創建的線程Thread-0!!!麼麼噠
thread1 修改成員屬性---> 2
thread2 修改成員屬性---> 3

方式3:通過實現Callable接口創建線程,重寫call()方法實現業務

@Data
@ToString
public class User {

    private String name;

    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

  }

public class ThreadDemo2 implements Callable<User> {

    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    @Override
    public User call() throws Exception {
        System.out.println("嗨!我是實現Callable<T> 接口創建的線程"+Thread.currentThread().getName()+",我有返回值哦,麼麼噠!!!");
        return new User("麼麼噠", 1);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadDemo2 threadDemo2 = new ThreadDemo2();
        FutureTask<User> futureTask = new FutureTask<User>(threadDemo2);
        new Thread(futureTask).start();
        User user = futureTask.get();
        System.out.println(user);
    }
}

嗨!我是實現Callable<T> 接口創建的線程Thread-0,我有返回值哦,麼麼噠!!!
User(name=麼麼噠, age=1)

方式4:使用線程池例如用Executor框架,後續再詳解

幾種方式創建線程優缺點比較

通過繼承Thread:

      優點:編寫簡單,如果需要訪問當前線程,則無須使用Thread.currentThread()方法,直接使用this即可獲得當前線程;

      缺點:繼承了Thread不能再繼承其他類,擴展性不好,不適應於多線程;

通過實現Runnable接口:

      優點:可以繼承其他類、擴展性好;通過上面thread1、thread2操作money可以看出,實現接口可以多線程target的共享,適用於多線程;

      缺點:編程稍稍複雜,不能使用Thread的方法,如果需要訪問當前線程,則必須使用Thread.currentThread()方法;

通過實現Callable接口:

      優點:可以繼承其他類、擴展性好;多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,非常適合創建多線程;和Runnable不同的是,該方式有返回值,可以做一些業務處理;

      缺點:編程稍稍複雜,不能使用Thread的方法,如果需要訪問當前線程,則必須使用Thread.currentThread()方法;

總結:Thread編程簡單,不適用多線程環境;Runnable、Callable編程稍微複雜點,但是非常適合多線程環境;

            Callable有返回值;

線程常用方法介紹

        Thread.currentThead():獲取當前線程對象

  getPriority():獲取當前線程的優先級

  setPriority():設置當前線程的優先級

注意:線程優先級高,被CPU調度的概率大,不代表一定會運行,還有小概率運行優先級低的線程.

isAlive():判斷線程是否處於活動狀態 (線程調用start後,即處於活動狀態)

join():調用join方法的線程強制執行,其他線程處於阻塞狀態,等該線程執行完後,其他線程再執行;有可能被外界

中斷產生   InterruptedException 中斷異常。

public final void join() throws InterruptedException;

 

public final synchronized void join(long millis) throws InterruptedException;

 

public final synchronized void join(long millis, int nanos) throws InterruptedException;

public class ThreadJoinDemo {

    public static void main(String[] args) throws InterruptedException {
        final Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " : " + Thread.currentThread().getState());
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " : " + Thread.currentThread().getState());
            }
        });

        t1.start();
        t2.start();
        System.out.println(Thread.currentThread() + "wait " + t1.getName() + " " + t2.getName() + " over!");
        t1.join();
        t2.join();
        System.out.println(Thread.currentThread() + " final " + Thread.currentThread().getState());
        //情況1: main wait ---> main final ---> t1 ---> t2   (假設join不會進行cpu調度)
        //情況2: main wait ---> t1 ---> t2 ---> main final (假設Join會進行cpu調度)
        //結果
        //Thread[main,5,main]wait Thread-0 Thread-1 over!
        //Thread-1 : RUNNABLE
        //Thread-0 : RUNNABLE
        //Thread[main,5,main] final RUNNABLE
        //結論:join()會進行cpu調度,執行當前線程,其他線程進入阻塞狀態,等待當前線程執行完成!
        //可能出現以下結果,由於cpu時間片分批(cpu控制的),可以去人的是:main] final一定在t1/t2之後.
        //Thread-1 : RUNNABLE
        //Thread[main,5,main]wait Thread-0 Thread-1 over!
        //Thread-0 : RUNNABLE
        //Thread[main,5,main] final RUNNABLE
    }
}

sleep():Thread類的靜態方法,讓出執行權暫時不參與CPU調度,但是不會釋放鎖,當前調用線程進入阻塞狀態;當時間到了,重新進入就緒狀態,重新等待cpu調度.

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos) throws InterruptedException;

public class ThreadSleepDemo {
    public static void main(String[] args) throws InterruptedException {
        //創建一把鎖
        final Object lock = new Object();
        Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                //加鎖
                synchronized (lock) {
                    System.out.println("t1  :" + Thread.currentThread().getName() + " : " + Thread.currentThread().getState());
                    //在哪個線程裏面調用,就代表當前線程,此處代表t1線程,(t1線程先休眠1秒讓出時間片)
                    Thread.sleep(1000);
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //加鎖
                synchronized (lock) {
                    System.out.println("t2  :" + Thread.currentThread().getName() + " : " + Thread.currentThread().getState());
                }
            }
        });

        t1.start();
        t2.start();
        System.out.println("main thread " + Thread.currentThread().getName() + " " + Thread.currentThread().getState());

        //情況1: main ---> t1 sleep() ---> t2 ---->t1   (假設sleep會釋放鎖)
        //情況2: main ---> t1 sleep() ---> t2 lock so t1 ---> t2 (假設sleep不會釋放鎖)
        //main thread main RUNNABLE
        //t1  :Thread-0 : RUNNABLE
        //t2  :Thread-1 : RUNNABLE
        //結論:sleep()沒有釋放鎖,注意main 和t1的順序可能是下面這樣的,這個要看cpu的調度,唯一可以確定的是t1一定在t2前面;
        //t1  :Thread-0 : RUNNABLE
        //main thread main RUNNABLE
        //t2  :Thread-1 : RUNNABLE
    }
}

yield():Thread的靜態方法,讓出剩餘時間片但不會釋放鎖,當前線程進入就緒狀態.(cpu可能還是會調用本線程)

public static native void yield();

public class ThreadYieldDemo {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " ---> yield start  ");
                    Thread.yield();
                    System.out.println(Thread.currentThread().getName() + " ---> yield over  ");
                }
            });
            t.start();
        }

        //分析:
        //情況1:yield start ---> yield over ---> yield start ... (yield()方法不會讓出剩餘時間片)
        //情況2:yield start ---> yield start ---> yield over (會出現連續...) (yield()方法讓出剩餘時間片)
        //結果:Thread-0 ---> yield start
        //Thread-2 ---> yield start
        //Thread-4 ---> yield start
        //Thread-4 ---> yield over
        //Thread-3 ---> yield start
        //Thread-1 ---> yield start
        //Thread-7 ---> yield start
        //Thread-1 ---> yield over
        //Thread-8 ---> yield start
        //Thread-8 ---> yield over
        //Thread-3 ---> yield over
        //Thread-6 ---> yield start
        //Thread-6 ---> yield over
        //Thread-2 ---> yield over
        //Thread-5 ---> yield start
        //Thread-0 ---> yield over
        //Thread-5 ---> yield over
        //Thread-9 ---> yield start
        //Thread-7 ---> yield over
        //Thread-9 ---> yield over
        //注:結果不是一定.
        //結論:1.yield會讓出時間片 2.調用yield的線程處於就緒狀態,可能還是會被cpu調用 3.不釋放鎖驗證機制同上.
    }
}

interrupt()、interrupted()、isInterrupted():Interrupt()中斷線程,打上中斷標識,線程仍會繼續執行;

interrupted():檢驗線程是否中斷,並且清除中斷標識;

isInterrupted():檢測線程是否中斷,不清除中斷標識;

注:調用一個打上中斷線程標識的線程會拋出異常

public void interrupt();

public static boolean interrupted();

public boolean isInterrupted();

public class ThreadInterruptDemo {

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                if (Thread.interrupted()) {
                    System.out.println(Thread.currentThread().getName() + " interrupted " + Thread.currentThread().getState());
                }
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " isInterrupted " + Thread.currentThread().getState());
                }

            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " isInterrupted " + Thread.currentThread().getState());
                }
                if (Thread.interrupted()) {
                    System.out.println(Thread.currentThread().getName() + " interrupted " + Thread.currentThread().getState());
                }

            }
        });
 Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                mainThread.interrupt();
                System.out.println(Thread.currentThread().getName() +" --->  run over !");
            }
        });

        t1.start();
        t2.start();
        t1.interrupt();
        t2.interrupt();
        t3.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + Thread.currentThread().getState());


        //分析t1:
        // 情況1:interrupted()不清除中斷標識,結果則應該爲interrupted ---> isInterrupted
        // 結果1:main RUNNABLE
        //Thread-0 interrupted RUNNABLE
        //結論1:interrupted()清除中斷標識,且可以斷定interrupt()並沒有中斷線程,只是打上標識.
        //分析t2:
        //情況2:如果isInterrupted()清除中斷標識,結果應該爲結果則應該爲 isInterrupted
        //結果2:main RUNNABLE
        //Thread-1 isInterrupted RUNNABLE
        //Thread-1 interrupted RUNNABLE
        //結論2:如果isInterrupted不會清除中斷標識
        //分析t3:
        //結論:調用一個被中斷的線程(例子中的main線程)會拋出異常.
    }

}

Thread-2 --->  run over !
main RUNNABLE
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1252)
	at java.lang.Thread.join(Thread.java:1326)
	at gitee.activity.thread.ThreadInterruptDemo.main(ThreadInterruptDemo.java:56)

wait()、notify()、notifyAll():該方法要在同步方法或者同步代碼塊中才使用的.

wait():調用線程等待,進入阻塞狀態,直到notify()、notifyAll()才能從新進入就緒狀態,等待cpu調度繼續執行;

public final void wait() throws InterruptedException 
public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void notifyAll();
public class ThreadWaitDemo {
    static Object lock = new Object();

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " run over  ");
                    lock.wait(2000);
                }
            }
        });

        final Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " run over ");
                    //lock.notify();
                }
            }
        });

        t1.start();
        t2.start();
        Thread.sleep(1000);
        if("TERMINATED".equals(t1.getState().toString()) && "TERMINATED".equals(t2.getState().toString())){
            System.out.println("未超時:"+Thread.currentThread().getName() + " wait " + t1.getName() + " wait " + t2.getName());
        }
        Thread.sleep(3000);
        if("TERMINATED".equals(t1.getState().toString()) && "TERMINATED".equals(t2.getState().toString())){
            System.out.println("超時:"+Thread.currentThread().getName() + " wait " + t1.getName() + " wait " + t2.getName());
        }

        //分析:
        //情況1:wait()不釋放鎖,結果應該爲: Thread-0 run over --->
        //情況2:wait()方法鎖,結果應該爲: Thread-0 run over ---> Thread-1 run over

        //結果:Thread-0 run over ...
        //Thread-1 run over ...
        //超時:main wait Thread-0 wait Thread-1
        //結論1:wait()方法鎖 ;
        //結論2:wait(long timeout) 超時自動喚醒.
        //結論3:notify()可以主動喚醒wait()線程.(//lock.notify();)
    }
}

 

sleep()與wait()區別與共同點

區別1:sleep()是Thread的方法,而wait()和notify()、notifyAll()是Object方法;

區別2:sleep()不會釋放鎖,wait()會釋放鎖;

共同點1:都是使調用線程進入阻塞狀態;

共同點2:都支持指定等待時間wait(long timeout)、join(long timeout).

 

併發編程優缺點

優點1:充分利用多核CPU的計算能力,性能得到提升,效率提高;

優點2:方便進行業務拆分,提升應用性能.

缺點1:當併發量沒有達到’臨界點‘時,併發編程頻繁的上下文切換反而降低性能;

缺點2:併發編程最大的問題,線程安全問題,共享變量的可見性問題是很大的挑戰.

 

CPU保證原子性操作(硬件層面)

 

經典案例i++

比如同時有2個線程執行這段代碼,假如初始時i的值爲0,那麼我們希望兩個線程執行完之後i的值變爲2,然而在多線程中-->

初始時,兩個線程分別讀取i的值存入各自所在的CPU的高速緩存當中,然後線程1進行加1操作,然後把i的最新值1寫入到內存。此時線程2的高速緩存當中i的值還是0,進行加1操作之後,i的值爲1,然後線程2把i的值寫入內存。

  最終結果i的值是1,而不是2。這就是著名的緩存一致性問題

爲了解決緩存不一致性問題,通常來說有以下2種解決方法:

  1)通過在總線加LOCK#鎖的方式

  2)通過緩存一致性協議

在早期的CPU當中,是通過在總線上加LOCK#鎖的形式來解決緩存不一致的問題。因爲CPU和其他部件進行通信都是通過總線來進行的,如果對總線加LOCK#鎖的話,也就是說阻塞了其他CPU對其他部件訪問(如內存),從而使得只能有一個CPU能使用這個變量的內存。比如上面例子中 如果一個線程在執行 i = i +1,如果在執行這段代碼的過程中,在總線上發出了LCOK#鎖的信號,那麼只有等待這段代碼完全執行完畢之後,其他CPU才能從變量i所在的內存讀取變量,然後進行相應的操作。這樣就解決了緩存不一致的問題。

  但是上面的方式會有一個問題,由於在鎖住總線期間,其他CPU無法訪問內存,導致效率低下。

  所以就出現了緩存一致性協議。最出名的就是Intel 的MESI協議,MESI協議保證了每個緩存中使用的共享變量的副本是一致的。它核心的思想是:當CPU寫數據時,如果發現操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發出信號通知其他CPU將該變量的緩存行置爲無效狀態,因此當其他CPU需要讀取這個變量時,發現自己緩存中緩存該變量的緩存行是無效的,那麼它就會從內存重新讀取。

CAS原子性操作

Compare And Set(或Compare And Swap),CAS是解決多線程並行情況下使用鎖造成性能損耗的一種機制,CAS操作包含三個操作數——內存位置(V)、預期原值(A)、新值(B)。如果內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值,否則一直循環(自旋)直到成功。

在java中可以通過鎖和循環CAS的方式來實現原子操作。Java中 java.util.concurrent.atomic包相關類就是 CAS的實現

 

代碼展示:

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.atomic.AtomicInteger;  
  
public class Counter {  
    private AtomicInteger ai = new AtomicInteger();  
    private int i = 0;  
  
    public static void main(String[] args) {  
        final Counter cas = new Counter();  
        List<Thread> ts = new ArrayList<Thread>();  
        // 添加100個線程  
        for (int j = 0; j < 100; j++) {  
            ts.add(new Thread(new Runnable() {  
                public void run() {  
                    // 執行100次計算,預期結果應該是10000  
                    for (int i = 0; i < 100; i++) {  
                        cas.count();  
                        cas.safeCount();  
                    }  
                }  
            }));  
        }  
        //開始執行  
        for (Thread t : ts) {  
            t.start();  
        }  
        // 等待所有線程執行完成  
        for (Thread t : ts) {  
            try {  
                t.join();  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
        System.out.println("非線程安全計數結果:"+cas.i);  
        System.out.println("線程安全計數結果:"+cas.ai.get());  
    }  
  
    /** 使用CAS實現線程安全計數器 */  
    private void safeCount() {  
        for (;;) {  
            int i = ai.get();  
            // 如果當前值 == 預期值,則以原子方式將該值設置爲給定的更新值  
            boolean suc = ai.compareAndSet(i, ++i);  
            if (suc) {  
                break;  
            }  
        }  
    }  
  
    /** 非線程安全計數器 */  
    private void count() {  
        i++;  
    }  
}  
//結果:  
非線程安全計數結果:9671  
線程安全計數結果:10000  

 

CAS採用自旋高效解決了原子性問題,存在三個缺點:

  • 循環時間時間長的話會造成cpu大的開銷:CAS自旋長時間不成功的話,會給CPU帶來非常大的執行開銷。
  • ABA問題:因爲CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。 從Java1.5開始JDK的 atomic包裏提供了一個類AtomicStampedReference 來解決ABA問題。這個類的 compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
public class CasABADemo {
    private static AtomicStampedReference<Integer> asr = new AtomicStampedReference<Integer>(1, 1);

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(1000);
                boolean flag = asr.compareAndSet(asr.getReference(), 2, asr.getStamp(), asr.getStamp() + 1);
                System.out.println(" 1 ---> 2 -->" + flag + " 結果:" + asr.getReference());
                flag = asr.compareAndSet(asr.getReference(), 1, asr.getStamp(), asr.getStamp() + 1);
                System.out.println(" 2 ---> 1 -->" + flag + " 結果:" + asr.getReference());
            }
        });

        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                int beforeStamp = asr.getStamp();
                System.out.println("修改前版本號:" + beforeStamp);
                Thread.sleep(2000);
                int afterStamp = asr.getStamp();
                System.out.println("修改後版本號:" + afterStamp);
                //此刻已經放手ABA,1-->2-->1
                boolean flag = asr.compareAndSet(asr.getReference(), 5, beforeStamp, asr.getStamp() + 1);
                System.out.println("版本號匹配不成功:" + flag + " 當前版本:" + asr.getStamp() + " 傳入的版本" + beforeStamp);
                flag = asr.compareAndSet(asr.getReference(), 5, afterStamp, asr.getStamp() + 1);
                System.out.println("版本號匹配成功:" + flag + " 當前版本:" + asr.getStamp() + " 傳入的版本" + beforeStamp + " 最終理想值:" + asr.getReference());
            }
        });
        t2.start();
    }
    //結果:
    // 修改前版本號:1
    // 1 ---> 2 -->true 結果:2
    // 2 ---> 1 -->true 結果:1
    //修改後版本號:3
    //版本號匹配不成功:false 當前版本:3 傳入的版本1
    //版本號匹配成功:true 當前版本:4 傳入的版本1 最終理想值:5
    //結論:通過添加版本好-->解決了ABA問題.
}
  • 只能保證一個共享變量的原子性:當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖。
public class CasDemo {

    public static AtomicInteger ai = new AtomicInteger(0);

    public static void add() throws InterruptedException {
        ai.addAndGet(1);
        Thread.sleep(1000);
        ai.addAndGet(9);
        System.out.println("計算和之後:" + ai.get());

    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            es.submit(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    add();
                }
            });
        }
        es.shutdown();
    }
    //分析:如果add()的輸出結果都爲10的整數倍,那麼說明AtomicInteger保證方法原子性;反之不報賬.
    //結果:
    // 計算和之後:32
    //計算和之後:32
    //計算和之後:32
    //計算和之後:50
    //計算和之後:41
    //結論:CAS保證單個共享變量的原子性操作,但是不保證多個共享變量原子性和成員方法的原子性.
}

 

JAVA對象組成

  • 對象頭
  • 實例數據
  • 對齊填充字節

對象頭的組成

  1. Mark Word
  2. 指向類的指針
  3. 數組長度(只有數組對象纔有)

Mark Word


Mark Word記錄了對象和鎖有關的信息,當這個對象被synchronized關鍵字當成同步鎖時,圍繞這個鎖的一系列操作都和Mark Word有關。

Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。

Mark Word在不同的鎖狀態下存儲的內容不同,在32位JVM中:

指向類的指針

該指針在32位JVM中的長度是32bit,在64位JVM中長度是64bit,java類信息(類長什麼樣子,有些什麼屬性和行爲)存放在元空間;

數組長度

只有數組對象纔有該值,該數據在32位和64位JVM中長度都是32bit。

 

實例數據

接下來實例數據部分是對象真正存儲的有效信息,也既是我們在程序代碼裏面所定義的各種類型的字段內容,無論是從父類繼承下來的,還是在子類中定義的都需要記錄下來。 這部分的存儲順序會受到虛擬機分配策略參數(FieldsAllocationStyle)和字段在Java源碼中定義順序的影響。HotSpot虛擬機 默認的分配策略爲longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),從分配策略中可以看出,相同寬度的字段總是被分配到一起。在滿足這個前提條件的情況下,在父類中定義的變量會出現在子類之前。如果 CompactFields參數值爲true(默認爲true),那子類之中較窄的變量也可能會插入到父類變量的空隙之中。

對齊填充

第三部分對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起着佔位符的作用。由於HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是對象的大小必須是8字節的整數倍。對象頭正好是8字節的倍數(1倍或者2倍),因此當對象實例數據部分沒有對齊的話,就需要通過對齊填充來補全。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章