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

  掌握Java中的多線程,必須掌握Java中的各類鎖,以及瞭解Java中線程池的運用。關於Java多線程基礎總結能夠參考個人這篇博文Java多線程總結(一)多線程基礎html

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

  

1、Java中鎖緩存

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

咱們先來看一下鎖的類圖:併發

 

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

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

一、同步鎖  post

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

二、獨佔鎖(可重入的互斥鎖) spa

  互斥,即在同一時間點,只能被一個線程持有;可重入,便可以被單個線程屢次獲取。什麼意思呢?根據鎖的獲取機制,它分爲「公平鎖」和「非公平鎖」Java中經過ReentrantLock實現獨佔鎖,默認爲非公平鎖。

三、公平鎖 

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

四、非公平鎖 

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

五、共享鎖    

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

六、讀寫鎖  

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

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

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

        //先獲得lock
        
        lock.lock();//而後獲取鎖
        try {
            //各類控制操做
        }catch(Exception e){
            
        }finally {
            lock.unlock();//解鎖
        }    

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

 

(2)LockSupport和Condition

一、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!

 

二、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

如上,用起來挺簡單的。

 

 

2、線程池

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

一、介紹

  可見,線程池的主要是由一個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();     

 

二、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。

  

三、Executors

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

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

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

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

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

  newScheduledThreadPool(int corePoolSize)  :  建立具備指定數目的線程池,能夠指定延時後執行任務,即便線程空閒,也被保持在線程池內。

  newSingleThreadScheduledExecutor()  :  建立一個只有一個單線程的線程池,能夠指定延時後執行任務

 

四、線程池的狀態

  線程池的狀態有五種——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

 

 

 若是以爲本文還不錯的話,麻煩點擊推薦哦!謝謝啦!

相關文章
相關標籤/搜索