多線程用來知足多線程編程特性的控制類

多線程的特性

多線程編程要保證知足三個特性:原子性、可見性、有序性java

  • 原子性:相似事務,一個操做或者多個操做,要麼所有執行而且不會被任何因素打斷,要麼所有不執行。
  • 可見性:當前一個線程訪問一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看到修改的值,在單線程就沒有這個概念
  • 有序性:程序執行的順序按照咱們代碼指定的順序來執行

多線程的控制類

爲了保證多線程的三個特性,java引入了不少線程控制機制數據庫

  • ThreadLocal:線程本地變量
  • 原子類:保證變量原子操做
  • Lock類:保證線程有序性
  • Volatile關鍵字:保證線程變量可見性

1.ThreadLocal

1.做用

  • ThreadLocal提供線程局部變量,即爲使用相同變量的每個線程維護一個該變量的副本
  • 當某些數據是以線程做爲做用域而且不一樣線程具備不一樣的數據副本的時候,能夠考慮採用ThreadLocal,好比數據庫鏈接Connection,每一個請求處理線程都須要,但又不相互影響,就是用ThreadLocal實現

2.經常使用方法

  • initialValue:副本建立方法
  • get:獲取副本方法
  • set:設置副本方法

3.應用實例

模擬一個銀行轉帳編程

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();
    }
}

4.實現原理

先從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

2.原子類

1.非原子類問題演示

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;

2.原子類解決非原子類問題

Integer有對應的原子類AtomicInteger:賦值改成 n=new AtomicInteger(0) ;n++改成n.getAndIncrement() 用get()取出值

3.原子類介紹

java的java.util.concurrent.atomic包裏提供了不少能夠進行原子操做的類

  • 更新基本類型:AtomicInteger、AtomicBoolean、AtomicLong
  • 更新數組類型:AtomicIntegerArray、AtomicLongArray
  • 更新引用類型:AtomicReference、AtomicStampedReference等
  • 更新屬性類型:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

3.Lock

1.Lock鎖的類圖介紹

2.可重入鎖

  • 可重入鎖就是線程能夠進入它已經擁有的鎖的同步代碼塊
  • 不可重入鎖就是線程請求它已經擁有的鎖時會阻塞

用代碼演示:

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();
            }
        }
    }
}

結果發現,重入鎖能夠被已擁有該鎖的線程重複獲取

3.讀寫鎖

多個線程能夠同時讀,可是讀的時候不能寫;多個線程不能夠同時寫,寫的時候也不能讀

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();
    }
}

運行結果咱們能夠看到,當線程一讀的時候,線程二是不能進行寫的,線程一讀完畢,線程二開始寫,而此時線程三也不能讀,只有等線程二寫完了才能夠讀

4.Volatile關鍵字

1.做用

一個共享變量被Volatile修飾以後,那麼就具有了兩層語義

  • 保證了不一樣線程對這個變量操做的可見性,當一個線程對其進行操做,對於其餘線程來講是當即可見的(不保證原子性)

  • 禁止進行指令重排序(保證變量所在行的有序性)

    • 什麼是指令重排序?

      例如

      int i = 0 ;
      i=1;i=2;i=3;
      //java會直接運行i=3忽略了前兩句,由於編譯器認爲這是無效操做,會自動進行優化重排序咱們的代碼指令
      Volatile int i = 0 ;
      i=1;i=2;i=3;
      //java會按照咱們的指令逐個進行,再也不重排序指令

2.應用場景

基於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
    }
}
相關文章
相關標籤/搜索