1. 程式人生 > >1.java 執行緒常用工具類

1.java 執行緒常用工具類

一 Timer 定時器

Timer 類主要是用來完成定時任務的功能,比如鬧鐘這種週期性變化的事物。

** 1 一個最簡單的定時器**

兩秒鐘後引爆一個定時炸彈。

    @Test
    public void test() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定時器爆炸");
            }
        },
        2000);//延遲兩秒執行任務
        while (true) ;//將執行緒設定為死迴圈,避免主執行緒執行結束
    }

2 設定一個週期性執行的鬧鐘

兩秒鐘後引爆一個炸彈,以後每過一秒引爆一個

 @Test
    public void test2() {
        new Timer().schedule(new TimerTask() {
                                 @Override
                                 public void run() {
                                     System.out.println("定時器爆炸");
                                 }
                             },
                2000,
                1000);//第二次及以後會每過1000ms執行一次任務。
        while (true) ;
    }

注意:如果我們要執行一個任務時,想要手動的去取消該任務,那麼我們可以使用 cancel()方法來取消。

  • Timer 類中的作用是將任務佇列中的全部任務都取消(一個 Timer 可以建立多個 TimerTask 任務)
  • TimerTask 類中的作用是將自身任務從任務佇列中取消

二 ThreadLocal的使用

ThreadLocal 是一個建立執行緒區域性變數的類。通常我們在建立一個變數的時候,該變數是可以被任何執行緒所訪問的。而使用 ThreadLocal 類建立的變數只能被建立變數的那個執行緒所訪問,其它執行緒訪問不到。

** 一個簡單的例子**

public class _6_ThreadLocal {
    @Test
    public void test() {
        ThreadLocal<String> local = new ThreadLocal<>();
        for (int i = 0; i < 5; ++i) {
            final int index = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (_6_ThreadLocal.class){
                        System.out.println("當前執行緒:" + index);
                        local.set("執行緒" + index + "的資料");
                        System.out.println(local.get());
                    }
                }
            }).start();
        }
        while (true);
    }
}

三 ThreadPool 執行緒池

ThreadPool 就相當於是一個處理任務的執行緒工廠,工廠內有很多執行緒(工人),每當有任務來的時候,執行緒就去執行任務,任務結束後執行緒(工人)就休息。

使用ThreadPool 是為了提高程式的吞吐能力,提高CPU的利用效率。執行緒池就好比是一個醫院,最初醫院會招聘固定數量的醫生(執行緒池的容量),當有患者來看病時,醫生就會去診斷患者,當所有的醫生都很忙碌時,醫院就會招聘額外的醫生,當患者減少時,醫院救會解聘那些額外招來的醫生。

醫生看病的簡單例子

public class _7_ThreadPool {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);//建立固定的執行緒池
//        ExecutorService threadPool = Executors.newCachedThreadPool();//建立自適應執行緒池 隨任務的數量變化
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//建立單一執行緒池
        for(int i = 0; i < 10; ++i) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行緒" + 
                Thread.currentThread().getName() + "正在診療第" + index + "個患者");
                }
            });
        }
    }
}

四 Lock

Lock 比傳統執行緒中的 synchronized 更加面向物件,更加方便。使用lock想要達到執行緒互斥的效果需要多個執行緒執行的程式碼塊必須要用同一個 Lock物件。 讀寫鎖:分為讀鎖和寫鎖,多個讀鎖之間不互斥,讀鎖和寫鎖之間互斥,寫鎖和寫鎖之間也互斥。

1 一個簡單鎖的例子

兩個執行緒不斷的輸出各自的字串,保證每個執行緒每次輸出字串的完整性。

public class _8_Lock {
        private static  Lock lock = new ReentrantLock();//建立鎖
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    new Ouput().output("HHHHHHHHHH");
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    new Ouput().output("aaaaaaaaa");
                }
            }
        }).start();
    }

  static  class Ouput{
        public  void output(String name) {
            lock.lock();//加鎖
            try {
                for(int i = 0; i < name.length(); ++i) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }finally {
                lock.unlock();//釋放鎖
            }
        }
    }
}

2 讀寫鎖的例子

簡單的快取實現

public class _9_CacheDemo {
    private Map<String, Object> data = new HashMap<>();
    public static void main(String[] args) {
        System.out.println(new _9_CacheDemo().getData("haha").toString());
    }

    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    public Object getData(String key) {
        Object value = null;
        rwl.readLock().lock();
        try {
            value = data.get(key);
            if (value == null) {
                rwl.readLock().unlock(); //如果快取無資料,關閉 readLock ,開啟 writeLock (1)
                rwl.writeLock().lock();
               try {
                   if (value == null) {//使用 if 判斷 value, 避免多個執行緒同時進入到(1)重複寫入
                       value = "hehe";
                   }
               }finally {
                   rwl.writeLock().unlock();
               }
            }
            rwl.readLock().lock();//開啟 readLock 是為了 finally 中的 unLock
        }finally {
            rwl.readLock().unlock();
        }
        return value;
    }
}

五 Condition

Condition 將 Objectwait()、notify()、notifyAll()方法分解成不同的物件,以便更好的和 Lock 組合使用(必須結合使用)。Lock 替代了 synchronizedCondition 也替代了 Obejct 所監視的方法。使用 Condition 的好處在於可以選擇性的去喚醒其它的執行緒,比如有三個執行緒A、B和C,需要現執行B執行緒在執行A執行緒,最後執行C執行緒,如果使用 Condition便可以很好的解決。

一個阻塞佇列的例子

public class _10_Condition {
    final Lock lock = new ReentrantLock();
    final Condition fullCondition = lock.newCondition();
    final Condition emptyCondition = lock.newCondition();

    final Object[] datas = new Object[10];
    int putIndex, takeIndex, count;

    public static void main(String[] args) {
        _10_Condition condition = new _10_Condition();

        //存資料執行緒
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; ++i) {
                    try {
                        Thread.sleep(new Random().nextInt(2000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    condition.put(i);
                }
            }
        }).start();
        //取資料執行緒
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; ++i) {
                    try {
                        Thread.sleep(new Random().nextInt(2000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    condition.get();
                }
            }
        }).start();
    }


    public void put(Object data) {
        lock.lock();
        try {
            while (count == datas.length) { //陣列已滿,阻塞 put
                System.out.println("the arrays is full");
                fullCondition.await();
            }
            datas[putIndex] = data;
            System.out.println(Thread.currentThread().getName() + " put the data-------> " + data);
            ++count;
            if (++putIndex == datas.length) {
                putIndex = 0;
            }
            emptyCondition.signal(); //陣列中已有資料,喚醒 take 去資料
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public Object get() {
        lock.lock();
        try {
            while (count == 0) {   //陣列為空,阻塞 take
                System.out.println("the arrays is empty");
                emptyCondition.await();
            }
            Object obj = datas[takeIndex];
            System.out.println(Thread.currentThread().getName() + " get the data " + obj);
            --count;
            if (++takeIndex == datas.length) {
                takeIndex = 0;
            }
            fullCondition.signal(); //陣列沒滿,喚醒 put 存資料
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return null;
    }
}

6 Semaphore

Semaphore維護了當前訪問的個數,提供同步機制,控制同時訪問的個數。SemaphoreThreadPool功能類似,但還是有區別的:

  • 執行緒池控制的是執行緒數量,而訊號量控制的是併發數量,雖然說這個看起來一樣,但是還是有區別的。訊號量的呼叫,當達到數量後,執行緒還是存在的,只是被掛起了而已。而執行緒池,同時執行的執行緒數量是固定的,超過了數量的只能等待。
  • 執行緒池是執行緒複用的;訊號量是執行緒同步的
  • 執行緒池是多個執行緒非同步執行任務,訊號量是控制任務中的多執行緒同步區域。

小例子

10個執行緒,3個坑,每次可進去三個執行緒,其餘的執行緒都等待,當有一個執行緒離開時,等待的執行緒才可以進入

public class _10_Semaphore {
    public static void main(String[] args) {
        Semaphore sp = new Semaphore(3);
        for(int i = 0 ; i < 10; ++i) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        sp.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"進入,當前有" + (3 - sp.availablePermits())+"個執行緒");
                    try {
                        Thread.sleep(new Random().nextInt(10000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"執行緒離開"+",當前有" + (3 - sp.availablePermits())+"個執行緒");
                    sp.release();
                }
            }).start();
        }
    }
}

作者:Acey 連結:https://www.jianshu.com/p/757c4aed2647 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。