併發 : 同時擁有兩個或多個線程,若是程序在單核處理器上運行,多個線程交替的換入或者換出內存,這些線程是同時存在的,每一個線程都處於執行過程當中的某個狀態,若是運行在多核處理器上,此時,程序中的每一個線程都將分配到一個處理器核上,所以能夠同時運行。
爲何須要cpu cache: cpu 的頻率太快,快到主存跟不上,這樣在處理器始終週期內,CPU經常須要等待主存,浪費資源。因此cache得出現,是爲了緩解CPU和內存之間速度的不匹配問題。
CPU cache 有什麼意義:
1) 時間局部性: 若是某個數據被訪問,那麼在不久的未來它可能被再次的訪問
2) 空間局部性: 若是某個數據被訪問是,那麼與他相鄰的數據塊可能會被很快的訪問
CPU多級緩存 --緩存一致性 (MESI--緩存協議)
用於保證多個CPU cache 之間緩存共享數據的一致
CPU多級緩存-亂序執行優化
處理器爲提升運算速度而作出違背代碼原有順序的優化
java內存模型規範(java Memory Model,JMM)
定義:java內存模型規範規定了一個線程如何以及什麼時候能夠看到由其餘線程修改過的共享變量的值,以及在必須時如何同步的訪問共享變量。
java內存中的堆: 能夠在運行時動態的分配運行時內存,所以運行效率相對慢一些。
java中的棧: 優點: 存取速度要比java 中的堆的 存取速度要快,棧中的數據是能夠共享的,可是棧中數據的生存期與大小是肯定的。棧中主要存放一些基本的數據變量。
一般狀況下,當cpu 須要讀取主存的時候,會先將主存中的數據讀取到緩存中,甚至會將緩存中的某些數據讀取到內部寄存器中,
在寄存器中執行某些操做。
java內存模型 - 同步的八種操做
lock(鎖定) : 做用於主內存的變量,把一個變量標識爲一條線程獨佔狀態
unlock(解鎖): 做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放出來以後才能夠被其餘的線程使用。
read(讀取): 做用於主內存的變量,把一個變量從主內存傳輸到線程的工做內存中,以便隨後的load動做使用。
load(載入): 做用於工做內存的變量,他把read操做從主內存中獲得的變量值放入到工做內存的變量副本中。
use(使用): 做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎
assign(賦值): 做用於工做內存的變量, 他把一個從執行引擎接受到的值賦值給工做內存的變量。
store(存儲): 做用於工做內存的變量,把工做內存中的一個變量的值傳輸到主內存中,以便隨後的write的操做
write(寫入): 做用於主內存的變量,它把store操做從工做內存中一個變量的值傳送到主內存的變量中 。
java內存模型--同步規則
若是要把一個變量從主內存中複製到工做內存,就須要按順序的執行read和load操做,若是把變量從工做內存中同步回以內存中,就要按順序的執行store 和 write 操做。但java 內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行。
不容許read和load、store和write操做之一單獨出現
不容許一個線程丟棄它的最近assign的操做,即變量在工做內存中改變了以後必須同步到主內存中。
不容許一個線程無緣由的(沒有發生過任何assign操做)把數據從工做內存同步回主內存中
一個新的變量只能在主內存中誕生,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操做以前,必須先執行過了assign和load操做
一個變量在同一時刻只容許一條線程對其執行lock操做,但lock操做能夠被同一線程重複執行屢次,屢次執行lock以後,只有執行相同次數的unlock操做,變量才被解鎖。lock和unlock必須是成對出現的。
若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前須要從新執行load和assign操做初始化變量的值。
若是一個變量事先沒有被lock操做鎖定,則不容許對他執行unlock操做,也不容許unlock一個被其餘線程鎖定的變量
線程安全性 :
原子性: 提供了互斥訪問,同一時刻只能有一個線程來對他進行操做
可見性: 一個線程對主內存的修改能夠及時的被其餘線程觀察到
有序性: 一個線程觀察其餘線程中的指令執行順序,因爲指令重排序的存在,該觀察結果通常雜亂無序
CAS線程安全底層原理: 使用了compareAndSwapInt方法,不斷的使用當前的數值與底層主存中的值進行比較,只有當想聽那個的時候,才進行返回。
actomicXXX類: 保證線程的安全性。 、
JDK1.8 : LongAdder 該類的做用和atomicLong 類的做用是同樣的。
LongAdder類的做用: 主要是對熱點數據的一個分離,進行單獨處理。
在低併發的時候,性能和atomicLong 的性能是一致的,可是高併發的時候,經過分散提升了性能,可是在統計的時候,若是有併發更新也會致使統計偏差。
對於須要生成準確的數值,具備全局惟一性數值的時候,atomicLong纔是惟一的選擇 。
atomicReference,AtomicReferenceFieldUpdate:
atomicReference:
AtomicStampReference: CAS的ABA問題 假如 某個數據被修改成A而後修改成B再修改成A,該數據的版本就會發生變化
原子性鎖:
synchronized: 依賴JVM (做用對象的做用範圍內同一時刻只能有一個對象對其進行操做)
Lock:依賴特殊的CPU指令,代碼實現,ReentrantLock
synchronized:
修飾代碼塊: 大括號括起來的代碼,做用於調用的對象
修飾方法: 整個方法,做用於調用的對象
修飾靜態方法: 整個靜態方法,做用於全部的對象
修飾類: 括號括起來的部分,做用於全部的對象
synchronized: 在方法定義中synchronized並非方法的一部分,因此當synchronized修飾的方法被繼承的時候,子類是沒有synchronized功能的。
可見性: 致使共享變量在線程間不可見的緣由:
線程交叉執行
重排序結合線程交叉執行
共享變量更新後的值沒有在工做內存與主存之間及時的更新
JMM關於synchronized的兩條規定 :
線程解鎖前,必須把共享變量的最新值刷新到內存中。
線程加鎖時,將清空工做內存中共享變量的值,從而使用共享內存時須要從主內存中從新進行讀取
volatile: 經過加入內存屏障和禁止重排序優化來實現
對volatile變量寫操做時,會在寫操做後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存中
對volatile變量讀操做時,會在讀操做前加入一條load屏障指令,從主內存中讀取共享變量
volatile不具有原子性:
適用的場景: 一、 對變量的寫錯作不依賴於當前的值
二、 該變量沒有包含在其餘變量不變的實時中
很適合作狀態標識變量
java內存模型中,容許編譯器和處理器對指令進行重排序,可是重排序的過程不會影響到單線程的執行,卻會影響到多線程的併發執行正確性
volatile、synchtroniezed、lock
程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做現行發生於書寫在後面的操做
鎖定規則: 一個unlock操做先行發生於後面對同一個鎖的lock操做
volatile變量規則: 對一個變量的寫操做先行發生於後面對這個變量的讀操做
傳遞規則: 若是操做A先行發生於操做B,而操做B又先行發生於操做C,則能夠得出操做A先行發生於操做C
線程啓動規則: Thread對象的start() 方法先行發生於此線程的每個動做
線程中斷操做: 對線程interrupt() 方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生
線程終止規則: 線程中全部的操做都先發生於線程的終止檢測,咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行
對象終結規則: 一個對象的初始化的完成會先行發生於他的finalized() 的方法的時開始
對象發佈:
發佈對象: 使一個對象可以被當前範圍以外的代碼所使用
對象逸出: 一種錯誤的發佈。當一個對象尚未構造完成時,就使他被其餘線程所見。 html
展現對象逸出代碼示例:java
package MyStudyTest; /** * 不管是顯示的引用仍是隱式的引用都會形成this的逸出,由於構造方法尚未初始化完成,其餘的線程就看到了該成員變量的值 * 錯誤緣由: 在對象尚未完成構造函數初始化以前就對其發佈 */ public class Example2 { private int thisCanBeEscape = 0; public Example2(){ new InnerClass(); } private class InnerClass{ public InnerClass(){ System.out.println("{}"+Example2.this.thisCanBeEscape); } }
若是一個對象是一個可變對象,就須要對其進行安全的發佈,不然就會形成線程的不安全。數組
安全發佈對象的四種方法:緩存
一、在靜態初始化函數中初始化一個對象的引用安全
二、將對象的引用保存到volatile類型域或者AtomicReference對象中。多線程
三、將對象的引用保存到某個正確構造對象的final類型域中併發
四、將對象的引用保存到一個有鎖保護的域中app
volatile關鍵字有兩個使用場景: 框架
一、 能夠作狀態標識量 (由於該關鍵字會將工做內存空間中的數據刷新到主內存中)函數
二、能夠進行雙重檢測(由於該關鍵字能夠限制寄存器發生指令重排--針對單例懶漢模式的雙重檢測機制)
package MyStudyTest; /** * 懶漢模式: * 單例實例在第一次使用的時候建立 * 線程不安全的,只適用於在單線程的狀況下使用 */ public class SingleTenExample { //私有構造函數,只有當構造函數式私有的時候,纔不會被其餘的引用隨便的建立出來 private SingleTenExample(){}; //單例 private static volatile SingleTenExample instance = null; //靜態工廠方法 /* public static SingleTenExample getInstance(){ if(instance == null){ return new SingleTenExample(); } }*/ //添加synchronized關鍵字,保證線程安全性 /* public static Synchronized SingleTenExample getInstance(){ if(instance == null){ instance = new SingleTenExample(); } }*/ //以上方法雖然實現了線程的安全性,可是在性能方面較差 //雙重同步鎖單例模式 //這種方式依然是線程不安全的 //內存寄存器依次完成的指令: // 一、memory = allocate() 分配對象的內存空間 // 二、ctorInstance() 初始化對象 // 三、instance = memory 設置instance指向剛分配的內存 // JVM和cpu優化,發生了指令重排 // 一、memory = allocate() 分配對象的內存空間 // 三、instance = memory 設置instance指向剛分配的內存 // 二、ctorInstance() 初始化對象 // /** * 雙重檢測機制不必定是線程安全的,緣由是因爲有指令重排發生的可能, * 當有兩個線程同時調用getinstance方法的時候,假如A線程恰好執行到instance = new SingleTenExample(),指令2和3發生了順序交換, * 那麼當線程B請求instance ==null的時候,就會默認已經建立了實例,返回instance,而實際上該線程尚未進行初始化,就會致使線程不安全 * * 注: 經過volatile關鍵字就能限制發生指令重排 */ public static SingleTenExample getInstance() { if (instance == null) { // 雙重檢測機制 // B synchronized (SingleTenExample.class) { // 同步鎖 if (instance == null) { instance = new SingleTenExample(); // A - 3 } } } return instance; } }
注:靜態域和靜態代碼款的執行順序不同,產生的結果就會不同
使用枚舉來實現單例模式,同時也是一種很安全的單例模式
package com.mmall.concurrency.example.singleton; import com.mmall.concurrency.annoations.Recommend; import com.mmall.concurrency.annoations.ThreadSafe; /** * 枚舉模式:最安全 */ @ThreadSafe @Recommend public class SingletonExample7 { // 私有構造函數 private SingletonExample7() { } public static SingletonExample7 getInstance() { return Singleton.INSTANCE.getInstance(); } private enum Singleton { INSTANCE; private SingletonExample7 singleton; // JVM保證這個方法絕對只調用一次 Singleton() { singleton = new SingletonExample7(); } public SingletonExample7 getInstance() { return singleton; } } }
對於爲何枚舉是最安全的詳見博客: https://www.cnblogs.com/wcgstudy/p/11408495.html
不可變對象:
不可變對象須要知足的條件:
一、對象建立之後,其狀態就不能修改
二、對象全部的域都是final類型的
三、對象時正確建立的(對象建立期間,this引用沒有逸出)
final: 能夠用來修飾類、對象、方法
修飾的類不能被繼承 ,final修飾的類中的全部方法都會被隱式的指定爲final方法
修飾方法: 一、鎖定方法不被繼承類修改 二、效率(在JDK早期版本中將方法做爲一個內嵌方法,來提高效率,可是當方法過於龐大時,效率不會被提高)
二、private修飾的方法會被隱式的轉換爲final修飾的方法
修飾變量: 一、基本數據類型變量 二、引用數據類型變量 (被初始化以後,不能指向另外的一個對象)
當final修飾map的時候,只是引用不容許指向另外的一個對象,可是裏面的值是容許進行修改的 。
java中其餘不可變對象的申明方法:
一、 Collections.unmodifiableXXX:Collection、list、set、Map
二、 Guava : ImmutableXXX: Collection、list、set、Map
被以上兩個方法修飾的容器中的數據是不可變的
代碼演示實例:
package com.mmall.concurrency.example.immutable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.mmall.concurrency.annoations.ThreadSafe; @ThreadSafe public class ImmutableExample3 { private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3); private final static ImmutableSet set = ImmutableSet.copyOf(list); private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4); private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder() .put(1, 2).put(3, 4).put(5, 6).build(); public static void main(String[] args) { System.out.println(map2.get(3)); } }
線程封閉: 就是將對象封裝到一個線程中,這樣即便這個對象是線程不安全的,也不會有線程不安全的問題存在
實現線程封閉的幾種方式:
一、AD-HOC線程封閉: 程序控制實現,最糟糕,能夠忽略
二、堆棧封閉: 使用局部變量,無併發問題
三、ThreadLocal 線程封閉: 特別好的線程封閉方法,ThreadLocal底層使用了一個Map實現了線程的封閉
線程不安全的類與寫法:
代碼演示: 關於stringbuilder
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.NotThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @NotThreadSafe public class StringExample1 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; public static StringBuilder stringBuilder = new StringBuilder(); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("size:{}", stringBuilder.length()); } private static void update() { stringBuilder.append("1"); } }
StringBuilder 和 StringBuffer 的區別:
SringBuffer 在底層實現的時候,使用了synchronized關鍵字,因此是線程安全的,可是在同一時刻只有一個線程可以調用該對象,就致使他的性能相對較差 。
simpleDataFormat 方法在多線程中的不安全性:
一、當將simpleDateFormat對象定義爲全局變量,進行多線程調用的時候,就會出現異常拋出。
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.NotThreadSafe; import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @NotThreadSafe public class DateFormatExample1 { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update() { try { simpleDateFormat.parse("20180208"); } catch (Exception e) { log.error("parse exception", e); } } }
解決方法:對象堆棧封閉,將對象聲明爲局部變量:
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @ThreadSafe public class DateFormatExample2 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); update(); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update() { try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20180208"); } catch (Exception e) { log.error("parse exception", e); } } }
在實際項目運行中最推薦使用的日期轉換方法: jodaTime
package com.mmall.concurrency.example.commonUnsafe; import com.mmall.concurrency.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @ThreadSafe public class DateFormatExample3 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final int count = i; executorService.execute(() -> { try { semaphore.acquire(); update(count); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } private static void update(int i) { log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate()); } }
線程安全同步容器 :
ArrayList --》 Vector,stack
HashMap --> hashtable (裏面的key和value不能爲空 ),currenthashMap
Collections.SynchronizedXXXX (List,Set,Map)
vector不安全的使用方法:
package com.mmall.concurrency.example.syncContainer; import com.mmall.concurrency.annoations.NotThreadSafe; import java.util.Vector; @NotThreadSafe public class VectorExample2 { private static Vector<Integer> vector = new Vector<>(); public static void main(String[] args) { while (true) { for (int i = 0; i < 10; i++) { vector.add(i); } Thread thread1 = new Thread() { public void run() { for (int i = 0; i < vector.size(); i++) { vector.remove(i); } } }; Thread thread2 = new Thread() { public void run() { for (int i = 0; i < vector.size(); i++) { vector.get(i); } } }; thread1.start(); thread2.start(); } } }
使用靜態工廠方法同步容器保證線程安全:
package com.mmall.concurrency.example.syncContainer; import com.google.common.collect.Lists; import com.mmall.concurrency.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.Collections; import java.util.List; import java.util.Vector; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j @ThreadSafe public class CollectionsExample1 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; private static List<Integer> list = Collections.synchronizedList(Lists.newArrayList()); public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { final int count = i; executorService.execute(() -> { try { semaphore.acquire(); update(count); semaphore.release(); } catch (Exception e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("size:{}", list.size()); } private static void update(int i) { list.add(i); } }
注: 在容器進行遍歷的時候不去作刪除操做,不然會拋出異常
package com.mmall.concurrency.example.syncContainer; import java.util.Iterator; import java.util.Vector; public class VectorExample3 { // java.util.ConcurrentModificationException private static void test1(Vector<Integer> v1) { // foreach for(Integer i : v1) { if (i.equals(3)) { v1.remove(i); } } } // java.util.ConcurrentModificationException private static void test2(Vector<Integer> v1) { // iterator Iterator<Integer> iterator = v1.iterator(); while (iterator.hasNext()) { Integer i = iterator.next(); if (i.equals(3)) { v1.remove(i); } } } // success private static void test3(Vector<Integer> v1) { // for for (int i = 0; i < v1.size(); i++) { if (v1.get(i).equals(3)) { v1.remove(i); } } } public static void main(String[] args) { Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); vector.add(3); test1(vector); } }
線程安全之併發容器:
ArrayList --> copyonwriteArrayList
copyonWriteArrayList : 當將原來的數組添加到該數組的時候,會先將原來的數組copy一份到如今的數組,等全部的操做作完以後,再將新的數組的索引指向原來的數組,從而保證數據的一致性。
全部的操做都是在鎖的保護下進行的。
缺點: 效率低下,耗費內存多,有可能會觸發GC
二、不能進行實時性的操做,數據跟新慢
工做特色: 一、讀寫分離 二、數據的最終一致性 三、讀操做不加鎖,可是寫操做枷鎖
HashSet 、treeSet --》 copyonWriteArraySet 、ConcurrentSkipListSet
ConcurrentSkipListSet 在作批量操做的時候,並不能保證線程的安全性,須要手動的加上同步鎖
hashMap、treeMap --》 concurrentHashMap 、concurrentskipListMap
currentHashMap 中是不容許出現空值的
AQS被稱爲JUC的核心,由一個雙向鏈表實現sync queue以及一個不是必須的單向列表condition queue
使用Node實現FIFO隊列,能夠用於構建鎖或者其餘同步裝置的基礎框架
利用了一個int類型表示狀態(表示獲取鎖的線程的數量 )
使用的方法是繼承
子類須要經過繼承並經過實現它的方法管理其狀態(acquire和release)方法的操縱狀態
能夠同時實現排它鎖和共享鎖(獨佔和共享)
AQS同步組件:
CountDownLatch
semaphore
CyclicBarrier
ReenTrantLock
condition
FutrueTask