走進JUC的世界

阿輝ya發表於2022-04-04

概念

同步鎖:synchronized、Lock區別

1、synchronized是不需要進行手動解鎖

2、synchronized可以鎖方法、鎖同步程式碼塊

3、synchronized是Java自帶關鍵字

4、Lock鎖是一個類且它擁有synchronized的所有功能還具備擴充套件

5、Lock鎖的實現類ReentrantLock可以實現公平和非公平鎖

6、Lock鎖需要手動加鎖和手動解鎖

7、synchronized不可中斷而Lock鎖可以實現中斷

  • synchronized

    • 當修飾方法時:鎖的是方法呼叫者(this)
    • 當使用static synchronized修飾方法時,鎖的是Class物件(類名.class)
    • 也可以使用程式碼塊方式來鎖取Class物件(類名.class)
  • Lock : 主要使用到的實現類ReentrantLock(可重入鎖)

    • ReentrantLock() -> 非公平鎖(預設)(所謂非公平鎖既是可以進行插隊操作)
    • ReentrantLock(true) -> 公平鎖(所謂公平鎖就是需要排隊,不可以進行插隊操作)

集合的執行緒不安全情況和解決方案

List : ArrayList不安全List,但是在單執行緒情況下是高效的!

多執行緒下錯誤案例

List<Integer> list = new ArrayList<>();

for (int i = 0; i < 30; i++) {
      final Integer temp = i;
        new Thread(()->{
            list.add(temp);
            System.out.println(list);
       }, String.valueOf(temp)).start();
  }

//結果出現併發修改異常ConcurrentModificationException
[0, 1, 2, 3, 5, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 18, 20, 21, 23, 22, 24, 25, 26, 27, 29, 28]
//Exception in thread "11" Exception in thread "15" Exception in thread "19" java.util.ConcurrentModificationException

解決方案

//1.使用集合安全類進行轉換
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 30; i++) {
            final Integer temp = i;
            new Thread(()->{
                list.add(temp);
                System.out.println(list);
            }, String.valueOf(temp)).start();
        }
//2.使用List對應的Vector
 List<Integer> list = new Vector<>();
        for (int i = 0; i < 30; i++) {
            final Integer temp = i;
            new Thread(()->{
                list.add(temp);
                System.out.println(list);
            }, String.valueOf(temp)).start();
        }
//3.使用CopyOnWriteArrayList
List<Integer> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 30; i++) {
            final Integer temp = i;
            new Thread(()->{
                list.add(temp);
                System.out.println(list);
            }, String.valueOf(temp)).start();
        }

Set集合:HashSet

多執行緒下錯誤案例

Set<Integer> set = new HashSet<>();
        for (int i = 0; i < 30; i++) {
            final Integer temp = i;
            new Thread(()->{
                set.add(temp);
                System.out.println(set);
            }, String.valueOf(temp)).start();
        }
// 結果:丟擲ConcurrentModificationException併發修改異常!

解決方案:

// 1.使用Collections.synchronizedSet安全集合包裝
 Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
// 2.使用CopyOnWriteArraySet
Set<Integer> set = new CopyOnWriteArraySet<>();

map集合:HashMap

Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < 30; i++) {
            final Integer temp = i;
            new Thread(()->{
               map.put(String.valueOf(temp), temp);
                System.out.println(map);
            }, String.valueOf(temp)).start();
        }
//結果:Exception in thread "6" java.util.ConcurrentModificationException併發修改異常

解決方案:

// 1.ConcurrentHashMap
Map<String, Object> map = new ConcurrentHashMap<>();
for (int i = 0; i < 70; i++) {
    final Integer temp = i;
    new Thread(()->{
       map.put(String.valueOf(temp), temp);
        System.out.println(map);
    }, String.valueOf(temp)).start();
}
// 2.Hashtable(效率低)

常用執行緒輔助類

CountDownLatch(減法計數器)

CountDownLatch countDownLatch = new CountDownLatch(10); // 傳入一個數字,要執行多少次
countDownLatch.countDown();  // 每次執行完一個任務後,進行減1操作

countDownLatch.await();  // 等待計數器歸零,只有等上面執行次數完畢後,才能執行後面的操作

CyclicBarrier(加法計數器)

CyclicBarrier cyclicBarrier = new CyclicBarrier(10);  // 初始化計數器容量,預設構造Runnable為null


// 當計數器到達10的時候,就執行Runnable裡面的具體操作
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
            @Override
            public void run() {
                System.out.println("executor other thing!");
            }
        });

cyclicBarrier.await();  // 等待計數器到達初始化計數器值,然後才能執行下面操作!

栗子:

//初始化cyclicBarrier加法計數器
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
     @Override
      public void run() {
            System.out.println("執行到第十個啦,完結!");
        }
  });

        for (int i = 1; i <= 10; i++) {
            int u = i;
            new Thread(()->{
                try {
                    System.out.println("執行到第" + u +"個了, 還剩" + (10 - u) + "個");
                    //每執行完一個執行緒就進行加一操作!當執行完第十個就觸發cyclicBarrier初始化中的Runnable介面實現
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

Semaphore(訊號量)

Semaphore : 一般用於限流情況

semaphore.acquire():獲得執行緒使用許可權

semaphore.release():釋放執行緒使用許可權

        Semaphore semaphore = new Semaphore(2);

        for (int i = 1; i <= 4; i++) {
            new Thread(() -> {
                try {
                    // 得到執行緒執行許可權,當執行緒數到達了訊號量初始化容量,其他執行緒就會等待(阻塞)當前執行緒執行完畢並釋放執行許可權才可繼續執行!
                    semaphore.acquire(); 
                    System.out.println("當前執行緒:" + Thread.currentThread().getName() + "開始執行...");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("當前執行緒:" + Thread.currentThread().getName() + "執行完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); //釋放執行緒執行許可權
                }

            }, String.valueOf(i)).start();
        }
    }

// 結果
當前執行緒:1開始執行...
當前執行緒:2開始執行...
當前執行緒:1執行完畢
當前執行緒:2執行完畢
// 到達訊號量最大容量,其他執行緒就進行等待(阻塞)
當前執行緒:3開始執行...
當前執行緒:4開始執行...
當前執行緒:3執行完畢
當前執行緒:4執行完畢

讀寫鎖

ReadWriteLock

主要使用到:ReentrantReadWriteLock(實現類)

概念

  • 讀寫鎖共存

    • 讀 -> 讀 可以共存
    • 讀 -> 寫 不能共存(不能邊修改邊讀取,就會出現讀取的資料不正確情況)
    • 寫 -> 寫 不能共存(可能出現一個執行緒正在修改原來的值,另一個執行緒也在修改原來的值,出現兩個執行緒修改後,最後讀取的資料不是自己修改的資料)
  • 獨佔/共享鎖

    • 獨佔鎖:也就是寫鎖,同一時刻只能有一個執行緒可以對資料進行寫的操作
    • 共享鎖:也就是讀鎖,同一時刻可以出現多個執行緒對資料進行讀取的操作,且讀取的資料都是同一份資料
//開啟兩個讀寫執行緒,分別進行寫和讀操作        
for (int i = 0; i < 5; i++) {
            final Integer temp = i;
            new Thread(()->{
                mapDemo.put(String.valueOf(temp), temp + 10000);
            }, "執行緒->" + String.valueOf(temp)).start();
        }

        for (int i = 5; i < 10; i++) {
            final Integer temp = i;
            new Thread(()->{
                mapDemo.get(String.valueOf(temp));
            }, "執行緒->" + String.valueOf(temp)).start();
       }

// 初始化讀寫鎖
  private volatile Map<String, Object> map = new ConcurrentHashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    public void put(String key, Object value) {
        //寫入加鎖
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "開始寫入.....");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "寫入完畢.....");
        }finally {
            //寫完釋放鎖
            readWriteLock.writeLock().unlock();
        }
    }

    public Object get(String key) {
        //讀取加鎖
        readWriteLock.readLock().lock();

        Object object = null;
        try {
            System.out.println(Thread.currentThread().getName() + "開始讀取----------->");
            object = map.get(key);
            System.out.println(Thread.currentThread().getName() + "讀取完成----------->");
        }finally {
		//讀取解鎖
            readWriteLock.readLock().unlock();
        }
        return object;
    }

// 執行結果:發現寫入的時候,總是隻有一個執行緒可以在同一時間進行寫入,而讀取可以多個執行緒同時讀取
執行緒->1開始寫入.....
執行緒->1寫入完畢.....
執行緒->0開始寫入.....
執行緒->0寫入完畢.....
執行緒->3開始寫入.....
執行緒->3寫入完畢.....
執行緒->2開始寫入.....
執行緒->2寫入完畢.....
執行緒->4開始寫入.....
執行緒->4寫入完畢.....
執行緒->5開始讀取----------->
執行緒->5讀取完成----------->
執行緒->7開始讀取----------->
執行緒->8開始讀取----------->
執行緒->8讀取完成----------->
執行緒->6開始讀取----------->
執行緒->9開始讀取----------->
執行緒->9讀取完成----------->
執行緒->7讀取完成----------->
執行緒->6讀取完成----------->

阻塞佇列

  • ArrayBlockingQueue
    • add()與offer()區別:add在超出容量時會丟擲異常,而offer則不會丟擲異常,而是拒絕新增到佇列中!
    • 移除區別(remove()與poll()區別):當佇列中無元素時,remove會丟擲異常,而poll則是返回null
    • 檢視隊首(element()與peek()區別):當佇列為空時,element會丟擲異常,而peek
  ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(4);   

        arrayBlockingQueue.add("A");
        arrayBlockingQueue.add("B");
        arrayBlockingQueue.add("C");
        arrayBlockingQueue.add("D");
//        arrayBlockingQueue.add("E");
        System.out.println(arrayBlockingQueue);
//結果:
[A, B, C, D]

/**
注意:
1. 當元素超過佇列的容量時,就會丟擲異常java.lang.IllegalStateException: Queue full
2. 當新增null時,丟擲空指標異常 java.lang.NullPointerException
3.使用offer代替add使用
*/
//1.錯誤案例:容量為4,但是新增了五個元素
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(4);

        arrayBlockingQueue.add("A");
        arrayBlockingQueue.add("B");
        arrayBlockingQueue.add("C");
        arrayBlockingQueue.add("D");
        arrayBlockingQueue.add("E");
        System.out.println(arrayBlockingQueue);
//結果:
Exception in thread "main" java.lang.IllegalStateException: Queue full

//2.錯誤案例:新增null資料,丟擲空指標異常
     ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(4);

        arrayBlockingQueue.add("A");
        arrayBlockingQueue.add("B");
        arrayBlockingQueue.add("C");
        arrayBlockingQueue.add(null);
        System.out.println(arrayBlockingQueue);
//結果:
Exception in thread "main" java.lang.NullPointerException
    
//3.使用offer代替add新增元素
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(4);
        arrayBlockingQueue.offer("A");
        arrayBlockingQueue.offer("B");
        arrayBlockingQueue.offer("C");
        arrayBlockingQueue.offer("D");
        arrayBlockingQueue.offer("E");
        System.out.println(arrayBlockingQueue);

//add與offer區別:add在超出容量時會丟擲異常,而offer則不會丟擲異常,而是拒絕新增到佇列中!
//結果:
[A, B, C, D]

  • ArrayBlockingQueue 延遲等待

    //延遲新增等待----------------> offer()
    ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(4);
    
    System.out.println(arrayBlockingQueue.offer("A"));
    System.out.println(arrayBlockingQueue.offer("B"));
    System.out.println(arrayBlockingQueue.offer("C"));
    System.out.println(arrayBlockingQueue.offer("D"));
    //延遲12秒新增,如果佇列已滿就返回false(表示新增失敗)
    System.out.println(arrayBlockingQueue.offer("E", 12, TimeUnit.SECONDS));
    
    System.out.println(arrayBlockingQueue);
    //結果:
    true
    true
    true
    true
    false
    [A, B, C, D]
        
    // 延遲取出等待-------------> poll()
            ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(4);
    
            arrayBlockingQueue.offer("A");
            arrayBlockingQueue.offer("B");
            arrayBlockingQueue.offer("C");
            arrayBlockingQueue.offer("D");
            arrayBlockingQueue.offer("E", 2, TimeUnit.SECONDS);
    
            System.out.println(arrayBlockingQueue.poll());
            System.out.println(arrayBlockingQueue.poll());
            System.out.println(arrayBlockingQueue.poll());
            System.out.println(arrayBlockingQueue.poll());
            System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS));
    // 結果:
    A
    B
    C
    D
    //延遲等待2秒鐘再彈出,如果佇列為空,就返回null
    null
    
  • 同步佇列(SynchronousQueue)

    • 特性:只能儲存一個物件/值,當存入之後必須等待取出之後才能進行再次存入

栗子:

new Thread(()->{
    try {
        synchronousQueue.put(1);
        System.out.println(Thread.currentThread().getName() + ":put  " + 1);
        synchronousQueue.put(2);
        System.out.println(Thread.currentThread().getName() + ":put  " + 2);
        synchronousQueue.put(3);
        System.out.println(Thread.currentThread().getName() + ":put  " + 3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } }
        , "put執行緒:").start();

new Thread(()->{
    try {
        System.out.println(Thread.currentThread().getName() + ":take -> " + synchronousQueue.take());
        System.out.println(Thread.currentThread().getName() + ":take -> " + synchronousQueue.take());
        System.out.println(Thread.currentThread().getName() + ":take -> " + synchronousQueue.take());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } }
        , "take執行緒:").start();

//結果:
put執行緒::put  1
take執行緒::take -> 1
put執行緒::put  2
take執行緒::take -> 2
put執行緒::put  3
take執行緒::take -> 3

執行緒池

  • 執行緒複用(節約了系統資源)

  • 控制最大併發數(當達到執行緒池容量,就需要等待其他執行緒完成,才能繼續進入)

  • 管理執行緒

  • Executors執行緒池

ExecutorService executorService = Executors.newSingleThreadExecutor(); // 單個執行緒的池子
ExecutorService executorService = Executors.newFixedThreadPool(10); //開啟十個固定執行緒的池子
ExecutorService executorService = Executors.newCachedThreadPool(); //可伸縮執行緒池, 如果執行緒池中執行緒已全被使用就建立新的執行緒池

newSingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor(); // 單個執行緒的池子
try {
    for (int i1 = 0; i1 < 10; i1++) {
        //執行執行緒
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 執行了執行緒..");
            }
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    //關閉執行緒池
    executorService.shutdown();
}
//結果:只有一個執行緒在重複利用執行
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-1 執行了執行緒..

newFixedThreadPool

ExecutorService executorService = Executors.newFixedThreadPool(5); //開啟十個固定執行緒的池子
try {
    for (int i = 0; i < 10; i++) {
        //執行執行緒
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 執行了執行緒..");
            }
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    //關閉執行緒池
    executorService.shutdown();
}
//結果:五個不同的執行緒重複使用
pool-1-thread-4 執行了執行緒..
pool-1-thread-1 執行了執行緒..
pool-1-thread-4 執行了執行緒..
pool-1-thread-3 執行了執行緒..
pool-1-thread-2 執行了執行緒..
pool-1-thread-5 執行了執行緒..
pool-1-thread-2 執行了執行緒..
pool-1-thread-3 執行了執行緒..
pool-1-thread-4 執行了執行緒..
pool-1-thread-1 執行了執行緒..

newCachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool(); //可伸縮執行緒池, 如果執行緒池中執行緒已全被使用就建立新的執行緒池

try {
    for (int i = 0; i < 10; i++) {
        //開啟執行緒
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 執行了執行緒..");
            }
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    //關閉執行緒池
    executorService.shutdown();
}
//結果:開啟新執行緒,當已開啟的執行緒執行完畢,放入池子中又可以進行使用,如果開啟的執行緒都還在執行中,就建立新的執行緒
pool-1-thread-1 執行了執行緒..
pool-1-thread-6 執行了執行緒..
pool-1-thread-5 執行了執行緒..
pool-1-thread-3 執行了執行緒..
pool-1-thread-4 執行了執行緒..
pool-1-thread-2 執行了執行緒..
pool-1-thread-8 執行了執行緒..
pool-1-thread-7 執行了執行緒..
pool-1-thread-9 執行了執行緒..
pool-1-thread-10 執行了執行緒..

執行緒池引數

  • 7大引數
public ThreadPoolExecutor(int corePoolSize,   //核心執行緒數
                          int maximumPoolSize, //最大執行緒數
                          long keepAliveTime,  //執行緒存活時間
                          TimeUnit unit,      //執行緒時間單元
                          BlockingQueue<Runnable> workQueue) {  //阻塞佇列
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory()   //預設執行緒工廠
         , defaultHandler);   //執行緒拒絕策略(當達到了最大執行緒數時,採用執行緒拒絕策略)
}

Spring自帶的任務執行器執行緒池

@Bean("scheduledTaskExecutor")
public ThreadPoolTaskExecutor scheduledTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  //建立任務執行器執行緒池
    executor.setCorePoolSize(3);//設定核心執行緒數
    executor.setMaxPoolSize(5); //設定最大執行緒數
    executor.setQueueCapacity(1024*100);  //設定一個佇列容量
    executor.setThreadNamePrefix("parking-index-task"); //執行緒名稱
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒絕策略
    executor.initialize(); //初始化執行緒池
    return executor;
}

執行緒四大拒絕策略應用場景

AbortPolicy: 當佇列中執行緒已滿,就丟擲異常
DiscardPolicy:當佇列滿了,就丟棄任務,不會丟擲異常
CallerRunsPolicy: 佇列已滿時,就使用呼叫者的執行緒去執行,當處理器關閉就丟棄此執行緒需求
DiscardOldestPolicy:當佇列滿了,去嘗試和較早的執行緒競爭,當最早的執行緒即將執行完成就把當前任務使用即將完成的執行緒執行

原始碼解釋:
AbortPolicy:
public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         解釋:總是把RejectedExecutionException。Params: r—請求執行的可執行任務e—嘗試執行該任務的執行器丟擲:RejectedExecutionException—always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

DiscardPolicy:
    public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         解釋:什麼都不做,這有丟棄任務r的效果。引數:r -請求被執行的可執行任務e -試圖執行該任務的執行程式
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
CallerRunsPolicy:
    public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         解釋:在呼叫者的執行緒中執行任務r,除非執行器已經關閉,在這種情況下,任務將被丟棄。引數:r—請求執行的可執行任務e—嘗試執行該任務的執行程式
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
DiscardOldestPolicy:
    public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         解釋:獲取並忽略執行器將執行的下一個任務(如果有一個任務立即可用),然後重試執行任務r,除非執行器被關閉,在這種情況下,任務r將被丟棄。引數:r—請求執行的可執行任務e—嘗試執行該任務的執行程式
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }

Cpu密集型

int availableProcessors = Runtime.getRuntime().availableProcessors();  //獲取Cpu核數,適合設定核心執行緒池的大小

IO密集型

int availableProcessors = Runtime.getRuntime().availableProcessors();   //
int maximumPoolSize = availableProcessors * 2;   // Io密集型一般設定為Cpu核數的兩倍,防止

ForkJoin

  • 任務拆分
public class DoMain extends RecursiveTask<Long> {

    private Long start;
    private Long end;
    private final Long threshold = 10_0000_0000L;

    public DoMain(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    /**
    	遞迴分解大資料,每次進行兩段兩段操作
    */
    @Override
    protected Long compute() {
        Long res = 0L;
        if ((end - start) > threshold) {
            Long middle = (end + start) / 2;
            //分兩次進行計算
            ForkJoinTask<Long> fork1 = new DoMain(start, middle).fork();
            Long res1 = fork1.join();
            ForkJoinTask<Long> fork2 = new DoMain(middle, end).fork();
            Long res2 = fork2.join();
            res = res1 + res2;
        } else {
            for (Long i = start; i < end; i++) {
                res += i;
            }
        }

        return res;
    }
}

  //這樣建立執行緒不規範,這裡只是簡易操作!
        new Thread(() -> {
            long l = System.currentTimeMillis();
            DoMain doMain = new DoMain(0L, 500_0000_0000L);
            ForkJoinTask<Long> submit = new ForkJoinPool().submit(doMain);
            try {
                System.out.println("forkJoin輸出結果:" + submit.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("forkJoin所用時間: " + (System.currentTimeMillis() - l));
        }).start();

        //這樣建立執行緒不規範,這裡只是簡易操作!
        new Thread(() -> {
            long start = System.currentTimeMillis();
            Long res = 0L;
            for (Long i = 0L; i < 500_0000_0000L; i++) {
                res += i;
            }
            System.out.println("普通迴圈輸出結果:" + res);
            System.out.println("普通所用時間" + (System.currentTimeMillis() - start));
        }).start();

計算對比

stream環輸出結果: 124999999750000000
stream所用時間2304
普通迴圈輸出結果:124999999750000000
普通所用時間14116
forkJoin輸出結果:124999999750000000
forkJoin所用時間: 14468

stream流計算

        new Thread(() -> {
            long start = System.currentTimeMillis();
            long longStream = LongStream.range(0L, 5_0000_0000L).parallel().reduce(0L, Long::sum);
            System.out.println("stream環輸出結果: " + longStream);
            System.out.println("stream所用時間" + (System.currentTimeMillis() - start));
        }).start();

volatile

  • 保證了可見性
  • 不保證原子性(也就是多執行緒情況下,無法保證同一個值被多個執行緒修改)
  • 保證了禁止指令重排(當程式啟動時,它可能並不是按照我們程式碼的順序執行,比如初始化,可能就不是按照我們寫的程式碼步驟來的,這就是指令重排,保證指令不重排就可以使用volatile關鍵字進行宣告)
/**
 * 1.使用volatile禁止指令重排
 * 2. 使用AtomicInteger原子類保證是原子操作
 */
public static volatile AtomicInteger num = new AtomicInteger();

public static void main(String[] args) {

    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                //進行加一操作
                num.getAndIncrement();
            }
        }).start();
    }

    //當執行緒數大於2時,暫停main執行緒,讓給其他執行緒執行
    while (Thread.activeCount() > 2) {
        Thread.yield();
    }
    System.out.println(num);

原子類操作原始碼

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

	//原子類底層程式碼,使用了CAS(比較替換演算法,也就是自旋鎖)
	// compareAndSwapInt底層是呼叫c++操作記憶體,對應的是native關鍵字
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

CAS簡單實現

栗子:

@SneakyThrows
public static void main(String[] args) {

    CasLock casLock = new CasLock();

    new Thread(()->{
        try {
            casLock.lock();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            casLock.unLock();
        }

    }, "Thread1").start();

    TimeUnit.SECONDS.sleep(2);

    new Thread(()->{
        try {
            casLock.lock();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            casLock.unLock();
        }

    }, "Thread2").start();

}


public static class CasLock {
    AtomicReference<Thread> lock = new AtomicReference<>();

    public void lock() {
        Thread thread = Thread.currentThread();
        if (lock.get() == null) { //拿到泛型中Thread的值進行比較
            System.out.println(thread.getName() + "----> 開始自旋...");
        }

        while (lock.compareAndSet(null, thread)) {

        }
    }

    public void unLock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "----> 解鎖成功!");
        //解鎖
        lock.compareAndSet(thread, null);
    }

}

相關文章