多線程編程要保證知足三個特性:原子性、可見性、有序性java
爲了保證多線程的三個特性,java引入了不少線程控制機制數據庫
模擬一個銀行轉帳編程
public class ThreadLocalDemo { //建立一個銀行,錢,取款,存款 static class Bank{ //使用ThreadLocal建立該變量,數據類型以泛型傳入 private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){ //使用initialValue初始化該值,每一個線程獲得該變量副本時,都是這個初始值 @Override protected Integer initialValue() { return 0; } }; //爲該變量副本賦值 public void set(Integer money){ threadLocal.set(threadLocal.get()+money); } //獲取該變量副本 public Integer get(){ return threadLocal.get(); } } //建立線程,執行轉帳 static class Transfer implements Runnable{ //銀行對象,封裝轉帳操做 private Bank bank; public Transfer(Bank bank) { this.bank = bank; } //模擬轉帳 @Override public void run() { for(int i =0 ;i<10;i++){ bank.set(10); System.out.println(Thread.currentThread().getName()+"帳戶餘額:"+bank.get()); } } } public static void main(String[] args) { Bank bank = new Bank(); Thread thread1 = new Thread(new Transfer(bank),"用戶A"); Thread thread2 = new Thread(new Transfer(bank),"用戶B"); thread1.start(); thread2.start(); } }
先從TreadLocal自己講起,先看到get方法(set方法相似)數組
public T get() { //獲取當前線程 Thread t = Thread.currentThread(); //拿到當前線程中的ThreadLocalMap對象 ThreadLocalMap map = getMap(t); if (map != null) { //取出值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //返回結果 return result; } } //若是當前線程中的ThreadLocalMap爲空,那麼對線程的ThreadLocalMap作一些初始化處理 return setInitialValue(); } //初始化處理 private T setInitialValue() { //返回ThreadLocal對象建立時的默認值 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } //該方法通常在建立時被重寫 protected T initialValue() { return null; }
由get方法可知,在線程中,是已經有了一個ThreadLocalMap對象的,那ThreadLocalMap對象是什麼呢安全
如圖,可知ThreadLocalMap內部其實是有一個Entry對象來存放數據多線程
ThreadLocal的get和set都是獲取當前線程,而後對其內部的ThreadLocalMap進行數據操做,也就是說在線程中的是已經初始化了一個ThreadLocalMap,去到源碼看看ide
能夠看到在Thread中有不少的構造器,拿咱們上述例子的構造器來講優化
//線程類中就組合了一個ThreadLocalMap對象 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //構造器 Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } //注意到這裏有一個inheritThreadLocals的布爾值 private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ………… //獲取當前線程(在例子中爲主線程) Thread parent = currentThread(); ………… //若是當前線程中存在ThreadLocalMap對象,則將其元素也賦值到新建線程的ThreadLocalMap對象中 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ………… }
//ThreadLocal類 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); } //能夠看到,就是將當前線程的ThreadLocalMap給到新建的線程 private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
小結:也就是說,重寫的initialValue方法,是當調用get和set時,爲線程中的ThreadLocalMap進行一個初始化的定製,在第一個調用get和set時,基本上都會爲線程調用一個initialValuethis
i++是一個非線程安全的操做,演示一個多線程對同一個變量++的例子atom
public class ThreadAtomicDemo { static private int n; public static void main(String[] args) throws InterruptedException { int j=0; while (j<100){ n=0; Thread thread1 = new Thread(new Runnable() { @Override public void run() { for(int i =0;i<3000;i++){ n++; } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for(int i =0;i<3000;i++){ n++; } } }); thread1.start(); thread2.start(); //加入主線程,主線程會等待該線程完成後繼續進行 thread1.join(); thread2.join(); System.out.println("n最終結果:"+n); j++; } } }
結果以下:
能夠看到,結果不是穩定的6000,而是會出現少加多加的狀況,由於i++實際在cpu中是分了三步,這是非原子類操做,多線程的狀況下就容易出現問題
tp1 = i; tp2 = i+1; i = tp2;
Integer有對應的原子類AtomicInteger:賦值改成 n=new AtomicInteger(0) ;n++改成n.getAndIncrement() 用get()取出值
java的java.util.concurrent.atomic包裏提供了不少能夠進行原子操做的類
用代碼演示:
public class Reentrant { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); for (int i =0 ;i<10;i++){ lock.lock(); System.out.println("加鎖次數:"+(i+1)); } for (int i =0 ;i<10;i++){ try { System.out.println("解鎖次數:"+(i+1)); }finally { lock.lock(); } } } }
結果發現,重入鎖能夠被已擁有該鎖的線程重複獲取
多個線程能夠同時讀,可是讀的時候不能寫;多個線程不能夠同時寫,寫的時候也不能讀
package com.JIAT.deadLock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWrite { private Map<String,String> map = new HashMap<String, String>(); private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); private ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); public String get(String key){ readLock.lock(); try{ System.out.println("讀操做已經加鎖,開始讀操做...."); Thread.sleep(3000); return map.get(key); }catch (InterruptedException e){ e.printStackTrace(); return null; }finally { System.out.println("讀操做已經解鎖"); readLock.unlock(); } } public void set(String key , String value){ writeLock.lock(); try { System.out.println("寫操做已經加鎖,開始寫操做"); Thread.sleep(3000); map.put(key,value); }catch (InterruptedException e){ e.printStackTrace(); }finally { System.out.println("寫操做完成,已經解鎖"); writeLock.unlock(); } } public static void main(String[] args) { final ReadWrite readWrite = new ReadWrite(); //主線程寫操做 readWrite.set("key","value"); //線程一讀操做 new Thread(){ @Override public void run() { System.out.println(readWrite.get("key")); } }.start(); //線程二寫操做 new Thread(){ @Override public void run() { readWrite.set("key1","value1"); } }.start(); //線程三讀操做 new Thread(){ @Override public void run() { System.out.println(readWrite.get("key")); } }.start(); } }
運行結果咱們能夠看到,當線程一讀的時候,線程二是不能進行寫的,線程一讀完畢,線程二開始寫,而此時線程三也不能讀,只有等線程二寫完了才能夠讀
一個共享變量被Volatile修飾以後,那麼就具有了兩層語義
保證了不一樣線程對這個變量操做的可見性,當一個線程對其進行操做,對於其餘線程來講是當即可見的(不保證原子性)
禁止進行指令重排序(保證變量所在行的有序性)
什麼是指令重排序?
例如
int i = 0 ; i=1;i=2;i=3; //java會直接運行i=3忽略了前兩句,由於編譯器認爲這是無效操做,會自動進行優化重排序咱們的代碼指令 Volatile int i = 0 ; i=1;i=2;i=3; //java會按照咱們的指令逐個進行,再也不重排序指令
基於Volatile做用,使用Volatile時須要知足如下兩點:
例如單例模式的雙重校驗
class Singleton{ private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //看看是否已經建立 if(instance==null){ //只讓單線程進行建立 synchronized(Singleton.class){ if(instance==null){ //此時若是沒有volatile關鍵字馬上刷新,那其餘線程有可能來不及看到最新的instance就已經經過第一層校驗,就會再建立一個instance instance = new Singleton(); } } } return instance } }