(1)JUC併發實用工具的學習與應用

JUC的簡介

   在Java5.0提供了java.util.concurrent(簡稱JUC)包,在此包中增加了在併發編程中很常用的實用工具類,用於定義類似於線程的自定義子系統,包括線程池、異步 IO 和輕量級任務框架。提供可調的、靈活的線程池。還提供了設計用於多線程上下文中的 Collection 實現等。

volatile關鍵字與內存可見性

  • 內存可見性問題,當多個線程操作共享數據時,彼此不可見

  • volatile關鍵字:當多個線程進行操作共享數據時,可以保證內存中的數據可見(效率也低,但比synchronized的效率高,相較於synchronized是一種較爲輕量級的同步策略)jvm底層有重排序,用volatile修飾後就不可重排序了,使用代碼如下:

     注意
          1,volatile不具備“互斥性”
          2,volatile不能保證質量的“原子性”

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo).start();
        //運行,發現flag=true並沒有進來,解決方法一:加鎖,效率低
        while (true){
            if(threadDemo.isFlag()){
                System.out.println("----進來了-----");
                break;
            }
        }
    }

}
class ThreadDemo implements Runnable{
    //解決方法二:加volatile關鍵詞
    private volatile  boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag =true;
        System.out.println("flag = "+isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

CAS算法

  • CAS (Compare-And-Swap) 是一種硬件對併發的支持,針對多處理器操作而設計的處理器中的一種特殊指令,用於管理對共享數據的併發訪問,保證數據的原子性。

  • CAS 是一種無鎖的非阻塞算法的實現。

  • CAS 包含了 3 個操作數:
    1,需要讀寫的內存值 V
    2,進行比較的預估值 A
    3, 擬寫入的更新值 B

  • 當且僅當 V 的值等於 A 時,CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執行任何操作。代碼實現如下:

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

}

class AtomicDemo implements Runnable {

    //使用volatile不可解決
//    private volatile int number = 0;

    //利用原子變量解決(和包裝類有點相似)
    private AtomicInteger number = new AtomicInteger();

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getNumber());
    }

    public int getNumber() {
        return number.getAndIncrement();
    }
  • 模擬cas算法
public class JucTest {
    public static void main(String[] args) {
        final  CompareAndSwap cas = new CompareAndSwap();

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int expectedValue = cas.get();
                    boolean a = cas.compareAndSet(expectedValue,(int)(Math.random()*101));
                    System.out.println(a);
                }
            }).start();
        }
    }
}

class CompareAndSwap{
    private int value;

    //獲取內存值
    public  synchronized int get(){
        return value;
    }

    //比較,expectedValue預估值
    public  synchronized int compareAndSwap(int expectedValue, int newValue){
        int oldValue = value;
        if (oldValue == expectedValue){
            this.value = newValue;
        }
        return  oldValue;
    }

    //設置
    public synchronized boolean compareAndSet(int expectedValue, int newValue){
        return expectedValue == compareAndSwap(expectedValue,newValue);
    }
}

同步容器類ConcurrentHashMap

  • ConcurrentHashMap 同步容器類是Java 5 增加的一個線程安全的哈希表。對與多線程的操作,介於 HashMap 與 Hashtable 之間。內部採用“鎖分段”機制替代 Hashtable 的獨佔鎖。進而提高性能

  • ConcurrentSkipListMap 通常優於同步的 TreeMap。當期望的讀數和遍歷遠遠大於列表的更新數時,CopyOnWriteArrayList 優於同步的 ArrayList

  • CopyOnWriteArrayList/CopyOnWriteArraySet:”寫入並複製“
    注意:添加操作多時,效率低,因爲每次添加時都會進行復制,開銷非常的大。併發迭代操作多時可以選擇,代碼實現如下:

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

class TestThread implements Runnable {

    //java.util.ConcurrentModificationException
//    private static List<String> list = Collections.synchronizedList(new ArrayList<>());
    private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

    static {
        list.add("aa");
        list.add("bb");
        list.add("cc");
    }

    @Override
    public void run() {
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
            list.add("-aa-");
        }
    }
}

CountDownLatch閉鎖

  • CountDownLatch:閉鎖,在完成某些運算時,只有其他所有線程運算全部完成,當前運算才能執行,代碼如下:

    1, 確保某個計算在其需要的所有資源都被初始化之後才繼續執行。
    2, 確保某個服務在其依賴的所有其他服務都已經啓動之後才啓動。
    3,等待直到某個操作所有參與者都準備就緒再繼續執行。

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(5);
        LatchDemo ld = new LatchDemo(latch);
        Instant startTime = Instant.now();
        for (int i = 0; i < 5; i++) {
            new Thread(ld).start();
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
        }
        Instant endTime = Instant.now();
        System.out.println("耗費時間爲:" + Duration.between(startTime, endTime).toMillis() + "毫秒");
    }
}

class LatchDemo implements Runnable {

    private CountDownLatch latch;

    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {
        synchronized (this) {
            try {
                for (int i = 0; i < 50000; i++) {
                    if (i % 2 == 0) {
                        System.out.println(i);
                    }
                }
            } finally {
                latch.countDown();
            }
        }
    }
}

Callable接口

  • 實現多線程的第三種方式:Callable 接口,Callable 接口類似於 Runnable,兩者都是爲那些其實例可能被另一個線程執行的類設計的。相較於實現Runnable接口的方式,方法可以有返回值,並且可以拋出異常。Callable 需要依賴FutureTask ,FutureTask 也可以用作閉鎖。
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        //1,執行Callable方式,需要FutureTask實現類的支持,用於接收運算結果
        FutureTask<Integer> result = new FutureTask<>(td);
        new Thread(result).start();
        Integer sum = null;
        //2,接收線程運算後的結果
        try {
            sum = result.get(); //FutureTask可用於閉鎖
            System.out.println(sum);
            System.out.println("======================");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }
}

class ThreadDemo implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 1000; i++) {
            sum += 1;
        }
        return sum;
    }
}

同步鎖Lock

用於解決多線程安全問題的方式:

   1,同步代碼塊(synchronized:隱式鎖)
   2,同步方法(synchronized:隱式鎖)
   3,同步鎖Lock(注意:是一個顯示鎖,需要通過lock()方法上鎖,必須通過unlock()方法進行釋放鎖),代碼如下:

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket,"12306").start();
        new Thread(ticket,"飛豬").start();
        new Thread(ticket,"攜程").start();
    }
}

class Ticket implements Runnable{

    private int tick = 100;//總票數
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            lock.lock();//加鎖
            try {
                if (tick > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(Thread.currentThread().getName()+"完成售票,餘票爲:"+ --tick);
                }else {
                    System.out.println("票已全部售完");
                    break;
                }
            }finally {
                lock.unlock();
            }
        }
    }
}

Condition 控制通信

  • Condition 接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能。需要特別指出的是,單個 Lock 可能與多個 Condition 對象關聯。爲了避免兼容性問題,Condition 方法的名稱與對應的 Object 版本中的不同。

  • 在 Condition 對象中,與 wait、notify 和 notifyAll 方法對應的分別是await、signal 和 signalAll。

  • Condition 實例實質上被綁定到一個鎖上。要爲特定 Lock 實例獲得Condition 實例,請使用其 newCondition() 方法。代碼在下面生產者消費者案例中體現:

生產者消費者案例

  • 爲了避免虛假喚醒問題,應該總是使用在循環中,代碼如下:
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);
        new Thread(productor, "生產者AA").start();
        new Thread(productor, "生產者BB").start();
        new Thread(consumer, "消費者C").start();
        new Thread(consumer, "消費者D").start();
    }
}

//店員
class Clerk {
    private int product = 0;//數量
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //進貨
    public void get() {
        lock.lock();
        try {
            while (product >= 1) {
                System.out.println("商品已滿!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + ++product);
            condition.signalAll();
            this.notifyAll();
        } finally {
            lock.unlock();
        }

    }

    //賣貨
    public void sale() {
        lock.lock();
        try {
            while (product <= 0) {
                System.out.println("暫無貨物!");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + --product);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

//生產者
class Productor implements Runnable {

    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.get();
        }
    }
}

//消費者
class Consumer implements Runnable {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}

下一章,(2)JUC併發實用工具的學習與應用

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