Java多線程總結(二)鎖、線程池


  掌握Java中的多線程,必須掌握Java中的各種鎖,以及了解Java中線程池的運用。關於Java多線程基礎總結可以參考我的這篇博文Java多線程總結(一)多線程基礎

  轉載請注明出處——http://www.cnblogs.com/zrtqsk/p/3784049.html,謝謝。

  

一、Java中鎖

什么是鎖。鎖就是為了保護資源,防止多個線程同時操作資源時出錯的機制。

我們先來看一下鎖的類圖:

 

  如圖,Java中的鎖有兩個主要的根接口——Lock和ReadWriteLock,分別表示鎖和讀寫鎖。其中Lock的主要實現類是ReetrantLock。ReadWriteLock的主要實現類是ReetrantReadWriteLock。而ReetrantReadWriteLock讀寫鎖是通過兩個內部類——ReadLock和WriteLock實現的,其中ReadLock是共享鎖,WriteLock是獨占鎖。這兩個內部類都實現了Lock接口。

(1)、Java中的鎖主要有以下幾種概念:

1、同步鎖  

  同一時刻,一個同步鎖只能被一個線程訪問。以對象為依據,通過synchronized關鍵字來進行同步,實現對競爭資源的互斥訪問。

2、獨占鎖(可重入的互斥鎖) 

  互斥,即在同一時間點,只能被一個線程持有;可重入,即可以被單個線程多次獲取。什么意思呢?根據鎖的獲取機制,它分為“公平鎖”和“非公平鎖”Java中通過ReentrantLock實現獨占鎖,默認為非公平鎖。

3、公平鎖 

  是按照通過CLH等待線程按照先來先得的規則,線程依次排隊,公平的獲取鎖,是獨占鎖的一種。Java中,ReetrantLock中有一個Sync類型的成員變量sync,它的實例為FairSync類型的時候,ReetrantLock為公平鎖。設置sync為FairSync類型,只需——Lock lock = new ReetrantLock(true)

4、非公平鎖 

  是當線程要獲取鎖時,它會無視CLH等待隊列而直接獲取鎖。ReetrantLock默認為非公平鎖,或——Lock lock = new ReetrantLock(false)。

5、共享鎖    

  能被多個線程同時獲取、共享的鎖。即多個線程都可以獲取該鎖,對該鎖對象進行處理。典型的就是讀鎖——ReentrantReadWriteLock.ReadLock。即多個線程都可以讀它,而且不影響其他線程對它的讀,但是大家都不能修改它。CyclicBarrier, CountDownLatch和Semaphore也都是共享鎖

6、讀寫鎖  

  維護了一對相關的鎖,“讀取鎖”用於只讀操作,它是“共享鎖”,能同時被多個線程獲取。“寫入鎖”用於寫入操作,它是“獨占鎖”,只能被一個線程鎖獲取。Java中,讀寫鎖為ReadWriteLock 接口定義,其實現類是ReentrantReadWriteLock包括內部類ReadLock和WriteLock。方法readLock()、writeLock()分別返回度操作的鎖和寫操作的鎖。

(至於“死鎖”,並不是一種鎖,而是一種狀態,即兩個線程互相等待對方釋放同步監視器的時候,雙方都無法繼續進行,造成死鎖。)

 鎖的用法主要就是下面的流程:

        //先得到lock
        
        lock.lock();//然后獲取鎖
        try {
            //各種控制操作
        }catch(Exception e){
            
        }finally {
            lock.unlock();//解鎖
        }    

可以看到,這樣的用法比synchronized關鍵字依據對象同步,要方便簡單的多。

 

(2)LockSupport和Condition

1、LockSupport

  是用來創建鎖和其他同步類的基本線程阻塞原語。 LockSupport中的靜態方法park() 和 unpark() 的作用分別是阻塞線程和解除阻塞線程,而不會導致死鎖。演示如下:

package lock;

import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    static Thread mainThread = null;
    public static void main(String[] args) {
        //獲取主線程
        mainThread = Thread.currentThread();
        //新建線程並啟動
        MyThread thread1 = new MyThread("thread1");
        thread1.start();
        //模擬線程工作開始
        System.out.println(Thread.currentThread().getName() + "-----》 runs now!");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "-----》 running step " + i);
            //當前線程睡眠1秒
            sleepOneSecond();
            if(i == 2){
                System.out.println(Thread.currentThread().getName() + "-----》 now pack main thread——————————");
                //讓主線程阻塞
                LockSupport.park();
            }
        }
        System.out.println(Thread.currentThread().getName() + "-----》 run over!");
        
    }
    
    /**當前線程暫停一秒鍾 */
    public static void sleepOneSecond(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    static class MyThread extends Thread {

        public MyThread(String name){
            super(name);
        }
        
        @Override
        public void run() {
            synchronized (this) {
                //模擬工作開始
                System.out.println(Thread.currentThread().getName() + "-----》 runs now!");
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "-----》 running step " + i);
                    //當前線程睡眠1秒
                    sleepOneSecond();
                }
                //模擬工作結束
                System.out.println(Thread.currentThread().getName() + "-----》 run over!");
                
            }
            System.out.println(Thread.currentThread().getName() + "-----》 now unpack main thread———————— ");
            //解除主線程的阻塞
            LockSupport.unpark(mainThread);
        }       
    }
}

結果如下:

thread1-----》 runs now!
thread1-----》 running step 0
main-----》 runs now!
main-----》 running step 0
thread1-----》 running step 1
main-----》 running step 1
main-----》 running step 2
thread1-----》 running step 2
main-----》 now pack main thread——————————
thread1-----》 running step 3
thread1-----》 running step 4
thread1-----》 run over!
thread1-----》 now unpack main thread———————— 
main-----》 running step 3
main-----》 running step 4
main-----》 run over!

 

2、Condition

  對鎖進行精確的控制,可用來代替Object中的wait、notify、notifyAll方法,需要和Lock聯合使用。可以通過await(),signal()來休眠、喚醒線程。創建方式:Condition condition = lock.newCondition();

演示懶得自己寫了,參照http://www.cnblogs.com/skywang12345/p/3496716.html,如下:

package LockSupportTest;

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

public class ConditionTest {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {

        ThreadA ta = new ThreadA("ta");

        lock.lock(); // 獲取鎖
        try {
            System.out.println(Thread.currentThread().getName()+" start ta");
            ta.start();

            System.out.println(Thread.currentThread().getName()+" block");
            condition.await();    // 等待

            System.out.println(Thread.currentThread().getName()+" continue");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();    // 釋放鎖
        }
    }

    static class ThreadA extends Thread{

        public ThreadA(String name) {
            super(name);
        }

        public void run() {
            lock.lock();    // 獲取鎖
            try {
                System.out.println(Thread.currentThread().getName()+" wakup others");
                condition.signal();    // 喚醒“condition所在鎖上的其它線程”
            } finally {
                lock.unlock();    // 釋放鎖
            }
        }
    }
}

結果如下:

main start ta
main block
ta wakup others
main continue

如上,用起來挺簡單的。

 

 

二、線程池

我們先來看一下線程池的類圖:

1、介紹

  可見,線程池的主要是由一個Executor接口統籌的。這個接口代表一個執行者,是一個典型的命令模式的運用。這個接口只有一個方法void execute(Runnable command),提交並執行任務。

  ExecuteService顧名思義,指的是Executor的服務類,繼承了Executor接口,提供了更詳細的控制線程的方法。

  AbstractExecutorService是一個抽象類,實現了ExecutorService大部分的方法。

  而我們最常用的ThreadPoolExecutor則繼承了ExecutorService

  ForkJoinPool是JDK7新增的線程池,也是繼承了這個線程類。

  ScheduledExecutorService這個接口繼承了ExecutorService,比ExecutorService新增了“延時”和“周期執行”的功能。

  ScheduledThreadPoolExecutor這個類則實現了ScheduledExecutorService接口,且繼承了ThreadPoolExecutor新增了“延時”和“周期執行”的功能

  Executors是一個線程池的工廠類,提供一系列靜態方法,用於創建各種不同功能的線程池或線程相關的對象。

  而線程池的使用,最基本的就是如下:  

         // 創建各種線程 
         Thread thread1 = new MyThread();
         Thread thread2 = new MyThread();
         Thread thread3 = new MyThread();

         // 創建線程池pool

         // 將線程放入池中進行執行
         pool.execute(thread1 );
         pool.execute(thread2 );
         pool.execute(thread3 );

         // 關閉線程池
         pool.shutdown();     

 

2、ForkJoinPool

  可見,整個線程池系列,說白了,也就3個類ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。一個是普通線程池,一個是新增了“延時”和“周期執行”的功能的線程池。那么ForkJoinPool是什么呢?

  ForkJoinPool為了是解決現在、未來計算機多核的問題。ExecuteService其他實現類基本都是基於單核下執行的,解決的是並發問題,而ForkJoinPool解決的是並行問題。ExcuteService中處於后面的任務需要等待前面任務執行后才有機會執行,而ForkJoinPool會采用work-stealing模式幫助其他線程執行任務。work-stealing模式——所有在池中的線程嘗試去執行其他線程創建的子任務,這樣就很少有線程處於空閑狀態,非常高效。

  ForkJoinPool除了可以執行Runnable任務外,還可以執行ForkJoinTask任務,即ForkJoinPool的execute方法可以傳入一個ForkJoinTask對象,這個任務對象跟Runnable的不同是,ForkJoinTask被放到線程內部的隊列里面,而普通的Runnable任務被放到線程池的隊列里面了。

  需要詳細了解ForkJoinPool,可以參考http://blog.csdn.net/aesop_wubo/article/details/10300273。

  

3、Executors

  Executors是一個線程池的工廠類,提供一系列靜態方法,用於創建各種不同功能的線程池或線程相關的對象。

  主要有如下的幾個靜態方法:

  newCachedThreadPool()  :  創建一個具有緩存功能的線程池,系統根據需要創建線程,這些線程被緩存在線程池中。

  newFixedThreadPool(int nThreads)  :  創建一個可重用的,具有固定線程數的線程池。

  newSingleThreadExecutor()  :  創建一個只有一個單線程的線程池。

  newScheduledThreadPool(int corePoolSize)  :  創建具有指定數目的線程池,可以指定延時后執行任務,即使線程空閑,也被保持在線程池內。

  newSingleThreadScheduledExecutor()  :  創建一個只有一個單線程的線程池,可以指定延時后執行任務

 

4、線程池的狀態

  線程池的狀態有五種——RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED

  (圖片出處:http://www.cnblogs.com/skywang12345/p/3509960.html)

  RUNNING  :  線程池處在RUNNING狀態時,能夠接收新任務,以及對已添加的任務進行處理。

  SHUTDOWN  :  線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務

  STOP  :  線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務

  TIDYING  :  當所有的任務已終止,ctl記錄的"任務數量"為0,線程池會變為TIDYING狀態。 當線程池變為TIDYING狀態時,會執行鈎子函數terminated()。terminated()在ThreadPoolExecutor類中是空 的,若用戶想在線程池變為TIDYING時,進行相應的處理;可以通過重載terminated()函數來實現。

  TERMINATED  :  線程池徹底終止,就變成TERMINATED狀態。

 

參考:http://www.cnblogs.com/skywang12345/p/java_threads_category.html

  http://blog.csdn.net/aesop_wubo/article/details/10300273

 

 

 如果覺得本文還不錯的話,麻煩點擊推薦哦!謝謝啦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM