java多線程學習(下)

線程相關類

ThreadLocal

ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命週期內其作用,減少同一個線程內多個函數或者組件之間的一些公共變量的傳遞的複雜度。

每個線程只能操作自己線程的數據,而不會影響其他線程的數據

基本方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

舉個例子:

import java.util.Random;

public class ThreadLocalTest {
    public static void main(String[] args) {
        //循環創建兩個線程
        for(int i=0;i<2;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + "get data:" + data);
                    //取得當前線程的示例,並存放數據
                    MyThreadScopeData.getThreadInstance().setName("" + data);
                    MyThreadScopeData.getThreadInstance().setAge("" + data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }

    static class A implements Runnable{
        public void get() {
            MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
            System.out.println("A from " + Thread.currentThread().getName() + " Name:"
                    + myData.getName() + ",Age:" + myData.getAge());
        }

        @Override
        public void run() {
            MyThreadScopeData myData=MyThreadScopeData.getThreadInstance();
            System.out.println("A from " + Thread.currentThread().getName() + " Name:"
                    + myData.getName() + ",Age:" + myData.getAge());
        }
    }

    static class B {
        public void get() {
            MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
            System.out.println("B from " + Thread.currentThread().getName() + " Name:"
                    + myData.getName() + ",Age:" + myData.getAge());
        }
    }
}
//此類的設計很關鍵,單例模式
class MyThreadScopeData {
    private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<>();
    private MyThreadScopeData() {
    }

    public static MyThreadScopeData getThreadInstance() {
        MyThreadScopeData instance = map.get();
        if (instance == null) {
            instance = new MyThreadScopeData();
            map.set(instance);
        }
        return instance;
    }

    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

運行結果:

以上便實現了不同線程之間多個變量的共享(Name、Age)

 

Lock接口

lock和synchronized的比較:

Lock提供了比synchronized方法和synchronized代碼塊更廣發的鎖定操作,Lock允許實現更靈活的結構,可以具有差別很大的屬性,並支持多個相關的Condition對象。

Lock是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨佔訪問,每次只能有一個線程對Lock對象加鎖。

Lock的兩個實現類:ReentrantLock(可重入鎖)和ReentrantReadWriteLock(可重入讀寫鎖)

這裏採用上一篇中的火車售票例子進行修改,代碼如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSaleTickets implements Runnable {
    private static int tickets = 100;
    private static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (tickets > 0) {//餘票充足
            try {
                Thread.sleep(150);//延遲
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
        if (tickets == 0) {
            System.out.println("票已售完!");
        }
    }

    static void sale() {
        lock.lock();//加鎖
        try {
            tickets--;
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "當前餘票:" + tickets);
            }
        } finally {
            lock.unlock();//釋放鎖
        }
    }

    public static void main(String[] args) {
        LockSaleTickets sst = new LockSaleTickets();
        new Thread(sst).start();
        new Thread(sst).start();
        new Thread(sst).start();
        new Thread(sst).start();

    }
}

Condition實現通信

Condition是用來替代傳統的Object的wait()、notify()實現線程間的協作,相比使用Object的wait()、notify(),使用Condition的await()、signal()這種方式實現線程間協作更加安全和高效。

·調用Condition的await()和signal()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間纔可以使用

 

·Conditon中的await()對應Object的wait();

·Condition中的signal()對應Object的notify();

前面說了通過wait和notify實現線程間的通信,這裏仍然是修改上一篇的那個例子,子線程循環10次,接着主線程循環100次,又接着回到子線程循環10次,再接着回到主線程又循環100次,如此循環50次。

(上篇地址:https://blog.csdn.net/qq_37094660/article/details/80472657#t8)

代碼如下:

package cn.thread;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionCommunication {

    public static void main(String[] args) {
        final Business business = new Business();
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 1; i <= 50; i++) {
                            business.sub(i);
                        }
                    }
                }
        ).start();
        for (int i = 1; i <= 50; i++) {
            business.main(i);
        }
    }

    static class Business {
        Lock lock = new ReentrantLock();//定義一個可重入鎖
        Condition condition = lock.newCondition();//condition對象依賴於Lock對象
        private boolean bShouldSub = true;

        public void sub(int i) {
            lock.lock();
            try {
                while (!bShouldSub) {
                    try {
                        condition.await();//當前線程進入等待狀態
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 10; j++) {
                    System.out.println("sub thread sequence of " + j + ",loop of " + i);
                }
                bShouldSub = false;
                condition.signal();//喚醒一個等待在Condition上的線程
            } finally {
                lock.unlock();
            }
        }

        public void main(int i) {
            lock.lock();
            try {
                while (bShouldSub) {
                    try {
                        condition.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 100; j++) {
                    System.out.println("main thread sequence of " + j + ",loop of " + i);
                }
                bShouldSub = true;
                condition.signal();
            } finally {
                lock.unlock();
            }
        }
    }
}

利用Lock實現緩存池

原理:數據存放在map對象中檢查方法內部是否有此數據,如果沒有就數據庫中查詢,如果有則直接傳給調用者

初代緩存:

private Map<String,Object> cache=new HashMap<>();
public Object getData(String key){
    Object value=cache.get(key);
    if(value==null){
        value = "aaa";//實際是去數據庫取數據,這裏簡化了
        cache.put(key,value);
    }
    return value;
}

分析:如果三個用戶(線程)同時來查數據,而且此時緩存中沒有數據,代碼執行value==null過後,三個用戶對應的線程則都要去數據庫中進行取數據,這樣同樣對數據庫造成了壓力,沒起到緩存的作用,因此在getData()方法前加上關鍵字synchronized(同步),表示讀數據必須同步執行

 

public synchronized Object getDate(String key){

但是由於多線程讀取數據的時候不需要互斥(如果緩存裏面有數據),大家自己讀自己的互不影響,寫數據的時候爲了保護數據才需要互斥,而這裏的getData()方法相當於讀取數據,加上synchronized關鍵字反而多餘,但是方法內部有寫數據的過程,如果不加synchronized關鍵字修飾,那麼該怎麼做呢?
聲明一個讀寫鎖

private ReadWriteLock rwl=new ReentrantReadWriteLock();

通過讀鎖寫鎖來控制讀數據間的共享與寫數據間的互斥

代碼如下:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

//緩存系統
public class CacheDemo {
    private Map<String, Object> cache = new HashMap<>();
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
    }

    /*
    數據存放在map對象中
    檢查方法內部是否有此數據,如果沒有就數據庫中查詢,如果有則直接傳給調用者
     */
    public Object getData(String key) {
        rwl.readLock().lock();//首先上一個讀鎖,多個線程來讀數據時,
        Object value = null;
        try {
            value = cache.get(key);//從緩存中取數據
            if (value == null) {//如果讀數據的時候發現此數據爲null,則釋放讀鎖,加寫鎖
                rwl.readLock().unlock();
                rwl.writeLock().lock();//若有多個線程執行至此步,只有第一個線程能lock,後面的線程都被堵住
                //只有當第一個線程的寫操作讀操作都執行完後,後面的線程才繼續執行
                try {
                    if (value == null) {//這個value==null是判斷第一個線程之後的線程,因爲第一個線程寫數據後,value已經有值了
                        value = "aaa";//實際是去queryDB(),數據庫操作
                    }
                } finally {
                    rwl.writeLock().unlock();//數據寫完後,釋放寫鎖
                }
                rwl.readLock().lock();
            }
        } finally {
            rwl.readLock().unlock();
        }
        return value;
    }
}

這樣就實現了讀和寫之間互斥,寫和寫之間互斥

 

 

 

原文出自:https://my.csdn.net/qq_37094660(如需轉載請註明出處)

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