Java 線程(Android Learning Note)

進程概念

進程(英語:process),是計算機中已運行程序的實體。程序本身只是指令,數(shù)據(jù)及其組織形式的描述,進程才是程序的真正運行實例。

線程概念

線程(英語:Thread),是操作系統(tǒng)運算調(diào)度的最小單位,它被包含在進程中,是進程中的實際運行單位。

進程和線程的關(guān)系

在面向線程設(shè)計的系統(tǒng)(如當代大多數(shù)操作系統(tǒng),Linux2.6及更新的版本)中,進程本身不是基本運行單位,而是線程的容器。同一進程中的多條線程,共享該進程中的全部系統(tǒng)資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進程中的多個線程有各自的調(diào)用棧(call stack),自己的寄存器環(huán)境(register context),自己的線程本地存儲(thread-local storage)。

我們可以在 Mac 上打開活動監(jiān)視器,查看進程數(shù)和對應(yīng)的線程數(shù),嘗試理解上面的概念。

活動監(jiān)視器

還可以看下阮一峰的這個博客,及其下面的部分評論,幫助理解概念。
進程與線程的一個簡單解釋

Java 線程的運行狀態(tài)

運行狀態(tài)
  1. 初始狀態(tài):在其他線程中,新建 Thread 。
  2. 可運行狀態(tài):調(diào)用該線程的 start 方法,使其變成可運行狀態(tài),在線程池中,等待獲取 CPU 資源執(zhí)行任務(wù),由操作系統(tǒng)調(diào)度。
  3. 運行中:操作系統(tǒng)調(diào)度該線程,獲得 CPU 資源執(zhí)行任務(wù)。
  4. 結(jié)束:run() 中的代碼執(zhí)行完畢,main 方法結(jié)束 。
  5. 阻塞狀態(tài):阻塞狀態(tài)結(jié)束即變成可運行狀態(tài)
    1. 等待用戶輸入
    2. sleep 線程睡眠
    3. thread2.join,等待其他線程 如 thread2 終止后,在恢復(fù)。
  6. 等待隊列
    1. synchronized 鎖中,使用 object.wait() 等待隊列,object.notify 喚醒或者到了等待時間,object.notify() 隨機喚醒在此對象監(jiān)視器上其中一個線程,object.notifyAll() 喚醒在此對象監(jiān)視器上的所有線程。
    2. ReentrantLock Condition await()/signal() 。
  7. 線程同步(互斥鎖)synchronized,ReentrantLock 等。
  8. 線程讓步,Thread.yield() 把資源讓給其他線程。線程狀態(tài)由運行中變成可運行。因為是 OS 自己調(diào)度的線程,所以不一定能起到讓步資源的目的。

Thread 基本用法

線程的構(gòu)造方法

Thread()
Thread(Runnable target)
Thread(String name)
Thread(Runnable target, String name)
...

線程的實現(xiàn)方法

  • 繼承 Thread ,實現(xiàn) run 方法。
class TestThread1 extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("testThread1 run end.");
    }
}
TestThread1 testThread1 = new TestThread1();
testThread1.start();
  • 實現(xiàn) Runnable 接口 和 run方法。
class TestThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("testThread2 run end.");
    }
}
Thread testThread2 = new Thread(new TestThread2(), "t2");
testThread2.start();
  • 內(nèi)部類 Runnable。
Thread testThread3 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("testThread3 run end.");
    }
});
testThread3.start();

啟動線程

TestThread1 testThread1 = new TestThread1();
testThread1.start();

終止線程

void interrupt ()
boolean isInterrupted ()

//示例:
public void test01() {
        
    testThread3 = new Thread(new Runnable() {
            
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("t3計數(shù) " + i);
                if (testThread3.isInterrupted()) {
                    break;
                }
            }
            System.out.println("testThread3 run end.");
        }
    });
    testThread3.start();
        
    for (int i = 0; i < 500; i++) {
        if(i == 499) {
            testThread3.interrupt();
            System.out.println("testThread3 interrupt.");
        }
    }
}

線程優(yōu)先級

void setPriority (int newPriority)
MAX_PRIORITY Constant Value: 10 (0x0000000a)
MIN_PRIORITY Constant Value: 1 (0x00000001)
newPriority 的值在 1 和 10 之間(包括1,10)

線程安全

互斥鎖

互斥鎖(英語:英語:Mutual exclusion,縮寫 Mutex)是一種用于多線程編程中,防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。該目的通過將代碼切片成一個一個的臨界區(qū)域(critical section)達成。臨界區(qū)域指的是一塊對公共資源進行訪問的代碼,并非一種機制或是算法。一個程序、進程、線程可以擁有多個臨界區(qū)域,但是并不一定會應(yīng)用互斥鎖。

線程鎖 - 參考文章 - JDK 5.0 中更靈活、更具可伸縮性的鎖定機制

synchronized 用法

  • 對類對象加鎖
private synchronized void increaseCount1() {
        shareData++;
}

private void increaseCount1() {
        synchronized (this) {
            shareData++;
        }
}
  • 對其他對象加鎖
private final Object mLock = new Object();
private void increaseCount1() {
        synchronized (mLock) {
            shareData++;
        }
}
  • 對類加鎖
private synchronized static void increaseCount1() {
        shareData++;
}

private synchronized void increaseCount1() {
        synchronized (this.getClass()) {
            shareData++;
        }
}

synchronized 的優(yōu)點:
使用簡單,易于理解,不必擔(dān)心鎖沒有釋放,JVM 會幫助釋放鎖。
synchronized 的問題:
無法中斷一個等候鎖的線程,無法通過等候鎖的順序來獲得鎖,鎖不被釋放就不會得到鎖。

ReentrantLock 用法

private int shareData;
private ReentrantLock mLock = new ReentrantLock();
public void increaseCount() {
    mLock.lock();
    try {
        shareData++;
    } finally {
        mLock.unlock();
    }
}
oublic void readCount() {
    mLock.lock();
    try {
        return shareData;
    } finally {
        mLock.unlock();
    }
}

ReentrantLock 的優(yōu)點:
實現(xiàn)了 Lock 接口,和 synchronized 相同的并發(fā)性和內(nèi)存語義,還多了輪詢鎖,定時鎖,可中斷鎖等候。在激烈爭用的情況下性能更好。ReentrantLock 默認是不公平鎖,公平鎖可以按照求鎖的順序獲得鎖,但是會耗損性能。
ReentrantLock 的缺點:
要在 finally 中釋放鎖,如果保護的代碼拋出異常,鎖將永遠無法釋放。

synchronized 和 ReentrantLock 該如何選擇:
優(yōu)先使用 synchronized ,除非發(fā)現(xiàn)需要用到 synchronized 不具備的特性(比如時間鎖等候、可中斷鎖等候、無塊結(jié)構(gòu)鎖、多個條件變量或者輪詢鎖)或是在特別激烈爭用的情況下,則適合使用 ReentrantLock。

ReentrantReadWriteLock 具有讀同步優(yōu)化

private int shareData;
private ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
public void increaseCount() {
    mLock.writeLock.lock();
    try {
        shareData++;
    } finally {
        mLock.writeLock.unlock();
    }
}
oublic void readCount() {
    mLock.readLock.lock();
    try {
        return shareData;
    } finally {
        mLock.readLock.unlock();
    }
}

ReentrantReadWriteLock 的特點:
讀鎖之間不互斥,讀鎖和寫鎖之間互斥,寫鎖和寫鎖之間互斥。適用于大量讀操作,少量寫操作的場景。

信號量

信號量(英語:Semaphore)又稱為信號量、旗語,是一個同步對象,用于保持在0至指定最大值之間的一個計數(shù)值。當線程完成一次對該 semaphore 對象等待時,該計數(shù)值減一;當線程完成一次對 semaphore 對象釋放時,計數(shù)值加一。當計數(shù)值為0,則線程等待該 semaphore 對象,直至該 semaphore 對象變成 signaled 狀態(tài)。semaphore 對象的計數(shù)值大于0,為 signaled 狀態(tài);計數(shù)值等于0,為 nonsignaled 狀態(tài)。

public void test02() {
    final Semaphore semaphore = new Semaphore(3);
    System.out.println("可用的信號量:" + semaphore.availablePermits()); 
    for (int i = 0; i < 50; i++) {
        final int flag = i;
        Thread testThread = new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println("test thread runing..." + flag);
                    Thread.sleep((long) (Math.random() * 3000));
                    semaphore.release();
                    System.out.println("可用的信號量:" + semaphore.availablePermits()); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        testThread.start();
    }
}

線程協(xié)作

  • Object wait&notify/notifyAll
    如果有多個 wait 線程,notify 只隨機喚醒其中之一,notifyAll 會喚醒所有的 wait 線程,被喚醒的線程在鎖池中,待 notify 的線程釋放完鎖之后,所有在鎖池的線程才去競爭鎖。
private final Object mLock = new Object();
private void increaseCount1() {
        synchronized (mLock) {
            mLock.wait();/mLock.notify();
            shareData++;
        }
}
  • Condition await&signal
private ReentrantLock mLock = new ReentrantLock();
private Condition mCondition = mLock.newCondition();
// 要在鎖的包裹下 lock()-await()/signal()-unlock()
mCondition.await();
mCondition.signal();

線程池

線程池(英語:thread pool):一種線程使用模式。線程過多會帶來調(diào)度開銷,進而影響緩存局部性和整體性能。而線程池維護著多個線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。這避免了在處理短時間任務(wù)時創(chuàng)建與銷毀線程的代價。線程池不僅能夠保證內(nèi)核的充分利用,還能防止過分調(diào)度??捎镁€程數(shù)量應(yīng)該取決于可用的并發(fā)處理器、處理器內(nèi)核、內(nèi)存、網(wǎng)絡(luò)sockets等的數(shù)量。

使用單線程,固定線程,動態(tài)線程或是自定義的,根據(jù)自己的業(yè)務(wù)需求和使用場景合理選擇。

單線程

可以保障任務(wù)的執(zhí)行順序,隊列是個無界隊列。

static ExecutorService newSingleThreadExecutor()
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 100; i++) {
        final int flag = i;
        Runnable runnable = new Runnable() {
                
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (Math.random() * 3000));
                    System.out.println(Thread.currentThread().getName() + " running ... " + flag);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        executorService.execute(runnable);
    }

流程圖:


singleThreadPool

固定線程數(shù)

創(chuàng)建過的線程可以復(fù)用,不過只能創(chuàng)建固定數(shù)量的線程,有需要長時間處理的任務(wù)可以考慮使用。

static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

public void testFixedThreadPool() {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 100; i++) {
        final int flag = i;
        Runnable runnable = new Runnable() {
                
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (2000));
                    System.out.println(Thread.currentThread().getName() + " running ... " + flag);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        executorService.execute(runnable);
    }
}

流程圖:


fixedThreadPool

動態(tài)線程數(shù)

在無空閑線程可用的情況下,可隨時新建線程,創(chuàng)建過的線程可以復(fù)用,池線程數(shù)支持 0-Integer.MAX_VALUE,超過缺省 60 s可以移除線程,適合任務(wù)時間短的異步任務(wù)。
如果是大量的需要長時間處理的任務(wù),因為會創(chuàng)建很多線程,效率會很慢。

static ExecutorService newCachedThreadPool()
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

public void testCachedThreadPool() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 100; i++) {
        final int flag = i;
        Runnable runnable = new Runnable() {
                
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (5000));
                    System.out.println(Thread.currentThread().getName() + " running ... " + flag);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        executorService.execute(runnable);
    }
}

流程圖:


CachedThreadPool

自定義線程池

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

public void testCustomThreadPool() {
    int N = 2;
    BlockingQueue<Runnable> blockingDeque = new ArrayBlockingQueue<Runnable>(10);
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(N + 1, N * 2, 1000, TimeUnit.MILLISECONDS, blockingDeque);
    for (int i = 0; i < 14; i++) {
        final int flag = i;
        Runnable runnable = new Runnable() {
                
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (1000));
                    System.out.println(Thread.currentThread().getName() + " running ... " + flag);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        poolExecutor.execute(runnable);
    }
}
  • corePoolSize(核心線程池大小),當提交一個任務(wù)到線程池,如果線程池中的線程數(shù)量小于 corePoolSize,線程池就會創(chuàng)建一個線程,即使有空閑的線程可以執(zhí)行任務(wù)。
  • maximumPoolSize(線程池最大大小),如果核心線程池已滿,任務(wù)會添加到任務(wù)隊列,任務(wù)隊列如果也滿了,而當前線程數(shù)小于 maximumPoolSize,則會創(chuàng)建新線程執(zhí)行任務(wù)。
  • keepAliveTime(空閑線程存活時間),如果空閑線程 idle 的時間大于 keepAliveTime 則移除該線程。
  • unit(時間的單位),單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
  • workQueue(BlockingQueue 阻塞隊列) 的常規(guī)策略:
    • 直接切換 (Direct handoffs),SynchronousQueue 就是這種隊列,它將任務(wù)直接交給線程,而不持有。如果沒有線程可以執(zhí)行這個任務(wù),那任務(wù)無法添加到隊列,所以只能創(chuàng)建新線程。這種策略可以避免在處理有內(nèi)部依賴的請求集合時被鎖住。直接切換的策略通常需要沒有限制的 maximumPoolSizes 線程池數(shù),比如 newCachedThreadPool 動態(tài)線程,所以需要設(shè)置 idle 時間來移除空閑超時的線程。這樣可以避免提交的任務(wù)被拒絕執(zhí)行。當要處理的任務(wù)一直比可用的線程多時,線程會無限創(chuàng)建下去。
    • 無界隊列(Unbounded queues),LinkedBlockingQueue 屬于無界隊列,newFixedThreadPool 使用的就是這個隊列,當 corePoolSize 的線程都在忙碌的時候,任務(wù)會被添加到這個隊列里等待執(zhí)行。當每個任務(wù)完全獨立于其他任務(wù)的時候,這個策略是合適的,它們的執(zhí)行不會相互干擾,不獨立就會有被鎖住的風(fēng)險。例如,在 Web 服務(wù)中,它有時可以處理突發(fā)的大量請求,但是當處理的任務(wù)比可用的線程一直多時,隊列里的任務(wù)會無限的增多。
    • 有界隊列(Bounded queues),ArrayBlockingQueue 屬于有界隊列,有界隊列在配合使用有限 maximumPoolSizes 的時候,有助于防止系統(tǒng)資源耗盡,但 maximumPoolSizes 的大小難以調(diào)整,控制。隊列大小和最大線程池大小需要相互折衷,使用大隊列,小池可以最大限度的減少 CPU 的使用率,系統(tǒng)資源使用和上線文切換的成本,但會導(dǎo)致吞吐量降低。使用小隊列通常需要大的線程池,這可以保持 CPUs 繁忙,但可能會突然遇到無法接受的調(diào)度開銷,這也會降低吞吐量。
  • threadFactory,幫助創(chuàng)建線程。
  • RejectedExecutionHandler handler(飽和策略),在 Executor 被 shut down 關(guān)閉的時候,新提交的任務(wù)會被拒絕。在使用了有限的最大線程池和有限的隊列時,如果兩項都飽和了,新任務(wù)也會被拒絕。這時,execute 調(diào)用RejectedExecutionHandler 的 rejectedExecution(Runnable, ThreadPoolExecutor) 方法,下面有四個預(yù)先定義好的策略供使用:
    • ThreadPoolExecutor.AbortPolicy(默認的),拒絕時拋出 RejectedExecutionException 異常。
    • ThreadPoolExecutor.CallerRunsPolicy ,調(diào)用 execute 的線程自己運行這個任務(wù),這提供了一個簡單的反饋控制機制,會降低新任務(wù)的提交速度。
    • ThreadPoolExecutor.DiscardPolicy,丟棄掉無法執(zhí)行的任務(wù)。
    • ThreadPoolExecutor.DiscardOldestPolicy,如果 executor 沒有被關(guān)閉,那將丟棄掉隊列最前面的任務(wù),然后在嘗試執(zhí)行(嘗試執(zhí)行,可能還會失敗,會重復(fù)該過程)。
    • 自定義策略,實現(xiàn) RejectedExecutionHandler。

流程圖:


CustomThreadPool

從隊列中移除任務(wù)

public void testCustomThreadPool() {
    int N = 2;
    BlockingQueue<Runnable> blockingDeque = new ArrayBlockingQueue<Runnable>(10);
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(N + 1, N * 2, 1000, TimeUnit.MILLISECONDS, blockingDeque);
    for (int i = 0; i < 14; i++) {
        final int flag = i;
        Runnable runnable = new Runnable() {
                
            @Override
            public void run() {
                try {
                    Thread.sleep((long) (1000));
                    System.out.println(Thread.currentThread().getName() + " running ... " + flag);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        poolExecutor.execute(runnable);
    }
    poolExecutor.getQueue().remove();
    poolExecutor.getQueue().remove();
    poolExecutor.getQueue().remove();
        /*
        pool-1-thread-2 running ... 1
        pool-1-thread-4 running ... 13
        pool-1-thread-3 running ... 2
        pool-1-thread-1 running ... 0
        pool-1-thread-1 running ... 9
        pool-1-thread-2 running ... 6
        pool-1-thread-4 running ... 7
        pool-1-thread-3 running ... 8
        pool-1-thread-4 running ... 12
        pool-1-thread-2 running ... 11
        pool-1-thread-1 running ... 10
        3,4,5 號任務(wù)被刪除,沒有執(zhí)行,該方法刪除隊列前面的任務(wù),0,1,2 已經(jīng)執(zhí)行所以無法刪除。
        */
    }

關(guān)閉線程池

void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
Java Thread Pool shutdown

獲取任務(wù)的直接結(jié)果

    public void testReturnTaskResult() {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<String> future0 = executorService.submit(new Callable<String>() {

            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName() + " callable running ... 0");
                return "0 task finished!";
            }
        });
        Future<?> future1 = executorService.submit(new Runnable() {
            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " runnable running ... 1");
            }
        });
        
        Future<?> future2 = executorService.submit(new Runnable() {
            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " runnable running ... 2");
            }
        }, "2 task finished!");
        
        
        try {
            System.out.println("future 0:" + future0.get());
            System.out.println("future 1:" + future1.get());
            System.out.println("future 2:" + future2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

Future.get() 方法會阻塞當前線程。

聊聊并發(fā)(三)——JAVA線程池的分析和使用 InfoQ
文章中談到如何合理的配置線程池,根據(jù)任務(wù)的性質(zhì),是 CPU 密集型,IO 密集型,還是混合型任務(wù),還有任務(wù)的優(yōu)先級,任務(wù)的執(zhí)行時間,和任務(wù)的依賴性,如數(shù)據(jù)庫連接。

其他參考

網(wǎng)易云課堂 - 微專業(yè) - Android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容