java 多線程求和的幾種實現方式。

前情提要

通過本文你可以收穫

  1. 通過多線程計數求和了解synchronized的應用場景,以及其他加鎖方式爲什麼不生效
  2. 框架 對象鎖和類鎖

1、同一時刻只有一個線程執行這段代碼
2、最基本的互斥同步手段
3、分類 一共有倆種鎖:
1、對象鎖
1、同步代碼塊鎖
2、方法鎖
3 類鎖

案例演示與分析:

1、不使用鎖 求和會出問題

public class MutilThreadCount implements  Runnable{
    static int sum = 0;
    @Override
    public void run() {
        //synchronized (this){
            for (int i = 0; i < 100000; i++) {
                sum++;
            }
        //}
    }

    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count = new MutilThreadCount();
        Thread t1 = new Thread(count);
        Thread t2 = new Thread(count);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

預期結果200000 結果小於200000

2、使用volatile也會出問題

public class MutilThreadCount implements  Runnable{
    static volatile int sum = 0;
    @Override
    public void run() {
        //synchronized (this){
            for (int i = 0; i < 100000; i++) {
                sum++;
            }
        //}
    }

    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count = new MutilThreadCount();
        Thread t1 = new Thread(count);
        Thread t2 = new Thread(count);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}
預期結果200000 結果小於200000
volititle有倆個特徵:
  1、變量對線程是可見的
 雖然變量對各個線程是可見的, 但是sum++不是原子的,包含了獲取值,自增,賦值 每一個操作都可能被其他線程打斷 修改了變量的值
  2、防止指令重排
  例子就是雙重懶加載的單例模式

img

3、不鎖同一個對象 求和會出問題

public class MutilThreadCount implements  Runnable{
    Object lock1 = new Object();
    Object lock2 = new Object();
    static int  sum = 0;
    @Override
    public void run() {
        synchronized (lock1){
            for (int i = 0; i < 100000; i++) {
                sum=sum+1;
            }
        }
        synchronized (lock2){
            for (int i = 0; i < 100000; i++) {
                sum=sum+1;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count = new MutilThreadCount();
        Thread t1 = new Thread(count);
        Thread t2 = new Thread(count);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}
預期:400000  結果小於400000  第二個求和循環使用lock1就正確了

4、在方法上使用synchronized 求和正確

public class MutilThreadCount implements  Runnable{
    static int sum = 0;
    @Override
    public void run() {
        synchronized (this){
            for (int i = 0; i < 100000; i++) {
                sum++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count = new MutilThreadCount();
        Thread t1 = new Thread(count);
        Thread t2 = new Thread(count);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}
預期200000結果200000

5、使用synchronized鎖類 求和正確(鎖類意味着鎖住了Class對象 這個類所有的實例都被鎖了 所以即使使用lock1和lock2也沒問題 )

public class MutilThreadCount implements  Runnable{

    static int  sum = 0;
    @Override
    public void run() {
        synchronized (MutilThreadCount.class){
            for (int i = 0; i < 100000; i++) {
                sum=sum+1;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count = new MutilThreadCount();
        Thread t1 = new Thread(count);
        Thread t2 = new Thread(count);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}


下面是新建倆個線程對象  但因爲是鎖的類 ,所以結果也正確,但是如果鎖this就不行了
public class MutilThreadCount implements  Runnable{

    private Object lock1  = new Object();
    private Object lock2  = new Object();

    static int  sum = 0;
    @Override
    public void run() {
        synchronized (MutilThreadCount.class){
            for (int i = 0; i < 100000; i++) {
                sum=sum+1;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count1 = new MutilThreadCount();
        MutilThreadCount count2 = new MutilThreadCount();

        Thread t1 = new Thread(count1);
        Thread t2 = new Thread(count2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}
預期20000  結果20000

6、使用AtomicInteger(更好的voltitle變量) 求和正確

public class MutilThreadCount implements  Runnable{
    static AtomicInteger sum = new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            sum.addAndGet(1);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MutilThreadCount count = new MutilThreadCount();
        Thread t1 = new Thread(count);
        Thread t2 = new Thread(count);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

7、使用CAS技術(p263) 學懂aqs需要對照着併發包一起學習

8、使用retreentlock

9、使用futuretask來做

voltitle講解的不錯

https://crossoverjie.top/%2F2018%2F03%2F09%2Fvolatile%2F
https://zhuanlan.zhihu.com/p/56191979

分析

爲什麼前面倆種結果不對?

sum++不是一個原子操作

分爲三步:

  • 讀取sum

  • 把sum+1

  • 把sum寫回內存

倆個線程會在這三步中任何一步交替執行,擾亂了sum的值

爲什麼使用volitile也不行?不是傳說中volitile是對線程可見的麼?

爲什麼最後一種執行結果沒問題:

在run方法中加了鎖關鍵字就正常輸出20000了

鎖this也就是意味着給當前對象加了鎖
synchronized可以簡單的理解爲同一時刻只有一個線程執行這段代碼

肘後備急

synchronized

  1. 方法拋出異常 jvm會釋放鎖

  2. synchronized 作用於非static的方法 就是鎖了對象 和作用於this一樣

作用於static方法就是鎖了類,鎖了類也就是鎖了這個類所有的對象實例,和synchronized(xx.class)作用一樣

  1. 可重入:從外層函數獲得鎖後,內層函數可以直接獲取該鎖

同一個方法,不是同一個方法,不是一個類的方法都具有可重入性質,重入意味着獲取鎖的粒度是線程 不是調用。

可重入原理:

  1. 不可中斷:

  2. 加鎖和釋放鎖的原理

  3. 可見性原理:

    需要了解Java內存模型 共享變量和線程變量

    synchronize釋放後 會把變量重新寫入主內存,另一個線程也會從主內存中把數據拷貝到線程內存,

    但是爲什麼volitile不行?

  4. synchronize缺陷 :

    1、釋放鎖的情況少,試圖獲得鎖不能設定超時,不能中斷一個正在試圖獲取鎖的線程

    lock更好 可以手動釋放鎖和設置超時

    2、不夠靈活 讀寫鎖更靈活

    3、無法知道是否成功獲取到鎖

  5. synchronize只會在倆種情況下釋放鎖:

    第一就是執行完成任務,第二是拋出出異常

  6. 使用注意

    鎖對象不能爲空:因爲鎖的信息是保存在對象頭中的

    作用域不要太大,會嚴重降低效率

    避免死鎖

  7. 如何選擇synchronize和Lock

    儘量不要使用這倆個,可以使用併發包下的

    優先使用synchronize 因爲代碼量少?

    如果需要Lock特性 那麼就使用Lock

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yFY7nzK7-1590563267129)(/Users/wwh/Library/Application Support/typora-user-images/image-20200303001609889.png)]

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