Java 多線程共享變量

範例1:啓動10個線程求連續自然數累加的和,使用線程本地變量ThreadLocal<Integer>

package com.contoso.demo1;

public class App {

    public static void main(String[] args) {
        // 啓動10個線程求連續自然數累加的和
        for (int i = 1; i <= 10; i++) {
            Counter counter = new Counter(i * 10);
            Thread t = new Thread(counter, "線程" + i);
            t.start();
        }
    }

}
package com.contoso.demo1;

public class Counter implements Runnable {

    private Integer last;

    public Counter(Integer last) {
        this.last = last;
    }

    //線程本地變量:每個線程都創建了一個自己訪問的內部副本變量,intLocal變量在各個線程中各自獨立互不干擾
    ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    @Override
    public void run() {
        for (int i = 0; i <= last; i++) {
            intLocal.set(intLocal.get() + i);
        }
        System.out.println(Thread.currentThread().getName() + "--" + intLocal.get());
    }

}

例如:

1   +   2   +   3   +   4   +   5   +   6   +   7   +   8   +   9   +   10    =‬ 55

1   +   2   +   3   +   4   +   5   +   6   +   7   +   8   +   9   +   10   +   11   +   12   +   13   +   14   +   15   +   16   +   17   +   18   +   19   +   20 =‬ 210

1   +   2   +   3   +   4   +   5   +   6   +   7   +   8   +   9   +   10   +   11   +   12   +   13   +   14   +   15   +   16   +   17   +   18   +   19   +   20   +   21   +   22   +   23   +   24   +   25   +   26   +   27   +   28   +   29   +   30 =‬ 465

run:
線程2--210
線程3--465
線程1--55
線程5--1275
線程4--820
線程8--3240
線程7--2485
線程9--4095
線程10--5050
線程6--1830
BUILD SUCCESSFUL (total time: 2 seconds)

範例2:一共10張票由3個售票窗口出售,每個窗口就是一個線程,也就是3個線程一共要售出10張票

package com.contoso.demo2;

public class App {

    public static void main(String[] args) {
        Ticket ticket = new Ticket(10);
        Thread t1 = new Thread(ticket, "售票窗口1");
        Thread t2 = new Thread(ticket, "售票窗口2");
        Thread t3 = new Thread(ticket, "售票窗口3");
        t1.start();
        t2.start();
        t3.start();
    }

}
package com.contoso.demo2;

public class Ticket implements Runnable {

    private static int count;
    
    public Ticket(int count){
        this.count= count;
    }

    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this) { //加上同步代碼塊讓賣票行爲是同步的,當一個線程執行時,讓其它線程等待
                if (count > 0) {
                    count--;
                    System.out.println(Thread.currentThread().getName() + "已賣出1張票,總票數還剩" + count + "張票");
                }
            }

        }

    }

}
run:
售票窗口1已賣出1張票,總票數還剩9張票
售票窗口3已賣出1張票,總票數還剩8張票
售票窗口2已賣出1張票,總票數還剩7張票
售票窗口1已賣出1張票,總票數還剩6張票
售票窗口3已賣出1張票,總票數還剩5張票
售票窗口2已賣出1張票,總票數還剩4張票
售票窗口1已賣出1張票,總票數還剩3張票
售票窗口3已賣出1張票,總票數還剩2張票
售票窗口2已賣出1張票,總票數還剩1張票
售票窗口1已賣出1張票,總票數還剩0張票
BUILD SUCCESSFUL (total time: 4 seconds)

範例3:101個線程求自然數累加和

package com.contoso;

public class MultiThreadSharedInt implements Runnable {

    private static Object obj = new Object();
    private static int sum = 0;

    private int intValue;

    public MultiThreadSharedInt(int intValue) {
        this.intValue = intValue;
    }

    @Override
    public void run() {
        synchronized (obj) {
            sum = sum + intValue;
            System.out.println(Thread.currentThread().getName() + " :" + sum);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 101; i++) {
            new Thread(new MultiThreadSharedInt(i)).start();
        }
    }

}
run:
Thread-0 :0
Thread-2 :2
Thread-1 :3
Thread-4 :7
Thread-3 :10
Thread-5 :15
Thread-7 :22
Thread-8 :30
Thread-9 :39
Thread-10 :49
Thread-6 :55
Thread-12 :67
Thread-13 :80
Thread-11 :91
Thread-14 :105
Thread-15 :120
Thread-16 :136
Thread-17 :153
Thread-18 :171
Thread-19 :190
Thread-20 :210
Thread-22 :232
Thread-21 :253
Thread-25 :278
Thread-26 :304
Thread-27 :331
Thread-28 :359
Thread-29 :388
Thread-30 :418
Thread-24 :442
Thread-31 :473
Thread-32 :505
Thread-33 :538
Thread-34 :572
Thread-23 :595
Thread-35 :630
Thread-36 :666
Thread-37 :703
Thread-38 :741
Thread-39 :780
Thread-40 :820
Thread-41 :861
Thread-42 :903
Thread-51 :954
Thread-45 :999
Thread-43 :1042
Thread-46 :1088
Thread-47 :1135
Thread-49 :1184
Thread-61 :1245
Thread-50 :1295
Thread-48 :1343
Thread-56 :1399
Thread-55 :1454
Thread-57 :1511
Thread-58 :1569
Thread-59 :1628
Thread-62 :1690
Thread-54 :1744
Thread-53 :1797
Thread-44 :1841
Thread-52 :1893
Thread-60 :1953
Thread-63 :2016
Thread-64 :2080
Thread-65 :2145
Thread-66 :2211
Thread-67 :2278
Thread-68 :2346
Thread-69 :2415
Thread-70 :2485
Thread-72 :2557
Thread-71 :2628
Thread-74 :2702
Thread-76 :2778
Thread-79 :2857
Thread-77 :2934
Thread-75 :3009
Thread-78 :3087
Thread-80 :3167
Thread-81 :3248
Thread-83 :3331
Thread-86 :3417
Thread-84 :3501
Thread-85 :3586
Thread-87 :3673
Thread-90 :3763
Thread-89 :3852
Thread-91 :3943
Thread-93 :4036
Thread-97 :4133
Thread-95 :4228
Thread-96 :4324
Thread-99 :4423
Thread-98 :4521
Thread-100 :4621
Thread-73 :4694
Thread-82 :4776
Thread-94 :4870
Thread-88 :4958
Thread-92 :5050
BUILD SUCCESSFUL (total time: 0 seconds)

範例4:遞增遞減操作

package com.contoso;

public class App {

    public static void main(String[] args) {
        Printer printer = new Printer();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                    printer.inc(); // 由子線程調用inc()方法,count變量不需要使用volatile修飾
                }
            }).start();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                public void run() {
                    printer.dec();// 由子線程調用dec()方法,count變量不需要使用volatile修飾
                }
            }).start();
        }
    }

    static class Printer {

        private int count = 10; // 子線程共享私有堆棧變量,如果是使用主線程修改count變量的值,此變量必須使用volatile修飾

        public synchronized void inc() {
            count++;
            System.out.println(Thread.currentThread().getName() + " 加操作 count = " + count);
        }

        public synchronized void dec() {
            count--;
            System.out.println(Thread.currentThread().getName() + " 減操作 count = " + count);
        }
    }
}
run:
Thread-0 加操作 count = 11
Thread-1 加操作 count = 12
Thread-2 加操作 count = 13
Thread-4 加操作 count = 14
Thread-3 加操作 count = 15
Thread-5 減操作 count = 14
Thread-6 減操作 count = 13
BUILD SUCCESSFUL (total time: 0 seconds)

範例5:緩存自增多線程主鍵

package com.contoso;

import java.util.concurrent.atomic.AtomicLong;

public class App {

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "    " + CachedID1.getNextID());
            }).start();
        }
        try {
            Thread.sleep(1000);
            System.err.println("\n");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                System.err.println(Thread.currentThread().getName() + "    " + CachedID2.getNextID());
            }).start();
        }
    }
}

class CachedID1 {

    private static volatile long nextID = 1;

    static synchronized long getNextID() {
        return nextID++;
    }
}

class CachedID2 {

    private static AtomicLong nextID = new AtomicLong(1);

    static long getNextID() {
        return nextID.getAndIncrement();
    }
}
run:
Thread-11    1
Thread-5    11
Thread-6    10
Thread-4    12
Thread-0    8
Thread-1    14
Thread-2    15
Thread-7    9
Thread-10    5
Thread-9    6
Thread-8    7
Thread-14    4
Thread-13    3
Thread-12    2
Thread-15    16
Thread-17    17
Thread-3    13
Thread-16    18
Thread-19    19
Thread-18    20


Thread-31    1
Thread-30    6
Thread-25    9
Thread-20    12
Thread-23    15
Thread-27    8
Thread-28    7
Thread-33    4
Thread-34    5
Thread-36    17
Thread-29    3
Thread-32    2
Thread-37    18
Thread-38    19
Thread-35    16
Thread-21    14
Thread-22    13
Thread-26    11
Thread-24    10
Thread-39    20
BUILD SUCCESSFUL (total time: 1 second)

範例6:

1)這是一個錯誤的小例子;

2)這是另外的一個錯誤的小例子;

3)最後纔是正確的範例。

(1)這是一個錯誤的小例子:


package com.myth;

public class CounterBadSync1 implements Runnable{
    static CounterBadSync1 instance = new CounterBadSync1();
    static volatile int i = 0;  // volatile關鍵字能保證變量的有序行,變量的可見性,但無法保證變量的原子

    public  void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) {
        for (int k = 0; k < 20; k++) {
            i = 0;
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            Thread t3 = new Thread(instance);
            Thread t4 = new Thread(instance);
            Thread t5 = new Thread(instance);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
            try {
                t1.join();
                t2.join();
                t3.join();
                t4.join();
                t5.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i = " + i);
        }
    }
}

 如果程序能正確的被執行,打印出來的信息應該都是 i = 50000000,但是會打印輸出如下錯誤的信息:

run:
i = 15250075
i = 15036833
i = 13782017
i = 14600152
i = 13297146
i = 14529569
i = 15578520
i = 13900965
i = 14065265
i = 14213863
i = 15111402
i = 12214092
i = 13549145
i = 17541327
i = 15118973
i = 14939221
i = 15826373
i = 15090387
i = 16516943
i = 13213272
BUILD SUCCESSFUL (total time: 24 seconds)

(2)這是另外的一個錯誤的小例子:

package com.myth;

public class CounterBadSync2 implements Runnable {

    static int i = 0;

    /*
    雖然在increase()方法中,申明瞭它是一個同步方法,
    執行這段代碼的5個線程都指向了不同的Runnable實例,
    線程t1會進入同步方法前加鎖自己的Runnable實例,
    線程t2會進入同步方法前也加鎖自己的Runnable實例,
    線程t3會進入同步方法前也加鎖自己的Runnable實例,
    線程t4會進入同步方法前也加鎖自己的Runnable實例,
    線程t5會進入同步方法前也加鎖自己的Runnable實例,
    它們使用的都是隻屬於自己的對象鎖,是5把對象鎖,而不是同一把鎖
     */
    public synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) {
        for (int k = 0; k < 20; k++) {
            i = 0;
            // 這5個線程的Runnable實例new CounterBadSync2()並不是同一個對象
            // 這5個線程使用的是5把不同的鎖,因此線程安全是無法保證的
            Thread t1 = new Thread(new CounterBadSync2());
            Thread t2 = new Thread(new CounterBadSync2());
            Thread t3 = new Thread(new CounterBadSync2());
            Thread t4 = new Thread(new CounterBadSync2());
            Thread t5 = new Thread(new CounterBadSync2());
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
            try {
                t1.join();
                t2.join();
                t3.join();
                t4.join();
                t5.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i = " + i);
        }
    }
}

如果程序能正確的被執行,打印出來的信息應該都是 i = 50000000,但是會打印輸出如下錯誤的信息:

run:
i = 18402609
i = 13534286
i = 18492036
i = 15961159
i = 16038141
i = 14664662
i = 21591830
i = 21603842
i = 22323913
i = 20924102
i = 20440839
i = 20972306
i = 21910758
i = 22119042
i = 21172682
i = 20026029
i = 20389744
i = 20588357
i = 20269292
i = 20352488
BUILD SUCCESSFUL (total time: 4 seconds)

(3)最後纔是正確的範例:

package com.myth;

public class CounterGoodSync3 implements Runnable {

    static CounterGoodSync3 instance = new CounterGoodSync3();
    static int i = 0;

    public synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) {
        for (int k = 0; k < 20; k++) {
            i = 0;
            // 這5個線程的Runnable實例instance是同一個對象,
            // 一個對象實例的同一把鎖,被5個線程調用,變量i的原子性是可以保證的。
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            Thread t3 = new Thread(instance);
            Thread t4 = new Thread(instance);
            Thread t5 = new Thread(instance);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();
            try {
                t1.join();
                t2.join();
                t3.join();
                t4.join();
                t5.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i = " + i);
        }
    }
}

如果程序能正確的被執行,打印出來的信息應該都是 i = 50000000,多次運行後程序都會打印輸出如下正確的信息:

run:
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
i = 50000000
BUILD SUCCESSFUL (total time: 12 seconds)

範例7:

package com.myth;

import java.util.concurrent.locks.ReentrantLock;

public class MultiThreadDemo implements Runnable {

    static ReentrantLock lock = new ReentrantLock(); //  使用可重入鎖保護共享變量
    static int i = 0;  //共享變量

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            lock.lock();
            try {
                i++;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int k = 0; k < 20; k++) {
            i = 0;
            MultiThreadDemo instance = new MultiThreadDemo();
            Thread t1 = new Thread(instance);
            Thread t2 = new Thread(instance);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.err.println("i = " + i);
        }
    }
}
run:
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
i = 20000000
BUILD SUCCESSFUL (total time: 13 seconds)

 

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