多線程
- 程序,線程之間的異同點
-
- 什麼是線程死鎖,如何避免死鎖
- 線程相關
-
- 線程池
- Atomic 原⼦類
- Threadocal變量
- volatile, Atomic 總結
- Java記憶體區域,Java記憶體模型,硬體記憶體模型
程序,線程之間的異同點
- 程序:是程式的一次執行過程,即一旦程式被載入到記憶體并準備運作,它就是一個程序。在Java 中,當我們啟動main 方法時,其實就是啟動了一個JVM的程序,main 方法所在的線程就是這個程序中的一個線程,也稱為主線程。
- 線程:單個程序中執行的每個任務就是一個線程,線程是程序執行的最小機關,一個程序在其執行的過程中可以産生多個線程,同一程序下的多個線程共享程序的堆和方法區,每個線程有自己的虛拟機棧(保證線程中的局部變量不被其他線程通路到),程式計數器(為了線程切換後能恢複到正确的執行位置),本地方法棧(保證線程中的本地方法中局部變量不被其他線程通路到)。
什麼是線程死鎖,如何避免死鎖
1.線程死鎖:多個線程同時被阻塞,他們中的一個或多個都在等待某個資源被釋放。線程無限期地阻塞。
産生死鎖的四個條件
- 互斥條件
- 請求并保持
- 不剝奪條件
- 循壞等待
如何避免線程死鎖
- 破壞互斥條件:無
- 破壞請求并保持:一次性地申請所有用到的資源
- 破壞不剝奪條件:占用部分資源的線程申請其他資源是時,如果申請不到。主動釋放它占有的資源
- 破壞循壞等待條件:按序申請資源
線程相關
線程的建立方式:
- 自定義類繼承Thread父類,重寫 run()方法
- 自定義類實作 Runable接口,将實作類作為 new Thread() 時的參數
- 自定義類實作 Callable 接口,再使用 FutureTask 封裝 Callable,将FutureTask 作為 new Thread()時的參數
- 使用線程池,建立好之後,将自定義的Runable 實作類/ Callable 實作類作為參數送出(Runable 用 .execute(),Callable 用 .submit())
sleep(), wait() 比較
相同:
兩者都可以暫停線程的執行
差別:
- sleep()屬于Thread類下的方法,沒有釋放鎖,sleep()執行完之後線程自動蘇醒
wait()屬于Object 類下的方法,釋放了鎖,wait()調用之後,線程不會自動蘇醒,必須等别的線程調用同一鎖對象上的.notify() 或者
.notifyAll() ⽅法
- wait ()通常被⽤于線程間互動/通信,sleep ()通常被⽤于暫停執⾏
synchronized 關鍵字
synchronized關鍵字解決的是多個線程之間通路資源的同步性,synchronized關鍵字可以保證被它修飾的⽅法或者代碼塊在任意時刻隻能有⼀個線程執⾏。
synchronized 修飾,一開始是使用重量鎖,是依賴對象内部的monitor對象鎖來實作的,而monitor又依賴作業系統的MutexLock(互斥鎖)來實作的,是以阻塞線程時,需要從使用者态到核心态的切換,由核心線程來完成;同樣線程喚醒時,也必須依靠核心線程來完成。
使用者态到核心态的之間的轉态轉換,需要進行系統中斷,保護和恢複執行上下文資訊,這些都會影響程式的性能。
是以JDK6之後有了鎖優化,即無鎖->偏向鎖->輕量級鎖->重量級鎖的鎖更新機制。
volatile關鍵字
訓示 JVM,這個變量是不穩定的,每次使⽤它都到主存中進⾏讀取
volatile 關鍵字的主要作⽤就是
- 保證變量的可⻅性 ;保證變量線上程間可見,對volatile變量所有的寫操作都能立即反應到其他線程中
- 防⽌指令重排序
底層原理
volatile 修飾的變量編譯成彙編代碼後,其相對普通變量的指派操作指令後,多了一行
lock addl $0x0,(%esp)
, lock字首的作用是将本處理器的緩存寫入記憶體,該寫入動作也會引起其他處理器或其他核心無效化其緩存。
相當于進行了 assign -> store -> write 三個操作,即Java 記憶體模型定義的第一條特殊規則
再加上Java 記憶體模型對于Volatile 型變量定義的第二條特殊規則,即必須是read -> load -> use,實作了volatile 型變量的可見性。
Java記憶體模型定義的第三條特殊規則實作了防止指令重排的功能。
線程池
建立線程池的方式
- 使用類 Executors來實作 我們可以建立四種類型的
ExecutorService executorService = Executors.newCachedThreadPool();
//适用于執行很多的短期異步任務的小程式,或者是負載較輕的伺服器。
ExecutorService executorService = Executors.newSingleThreadExecutor();
//SingleThreadExecutor适用于需要保證順序地執行各個任務;并且在任意時間點,不會有多個線程是活動的應用場景
ExecutorService executorService = Executors.newFixedThreadPool();
ExecutorService executorService = Executors.newScheduledThreadPool(int corePoolSize);
- 通過 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
線程池的7個參數
corePoolSize: 線程池核心線程大小
線程池中會維護一個最小的線程數量,即使這些線程處理空閑狀态,他們也不會被銷毀,除非設定了allowCoreThreadTimeOut。這裡的最小線程數量即是corePoolSize
- maximumPoolSize : 線程池的最大線程數量
keepAliveTime :空閑線程存活時間
一個線程處于空閑狀态,并且目前的線程數量大于corePoolSize,那麼在指定的這個時間之後,線程會被銷毀。
- unit 空閑線程存活時間的機關
workQueue 工作隊列:通過線程池的 execute() 方法送出的 Runnable 對象将存儲在該參數中。
其采用阻塞隊列實作 (BlockingQueue< Runnable > workQueue),Java 提供了 BlockingQueue 接口的7 種阻塞隊列的實作。以下是 Executors 封裝的四種功能線程池的 workQueue 的使用。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())); //基于連結清單的阻塞隊列,同ArrayListBlockingQueue類似,
//其内部也維持着一個資料緩沖隊列(該隊列由一個連結清單構成)
//如果構造一個LinkedBlockingQueue對象,而沒有指定其容量
//大小,LinkedBlockingQueue會預設一個類似無限大小的容量
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()); //SynchronousQueue是一個内部隻能包含一個元素的隊列。
//插入元素到隊列的線程被阻塞,直到另一個線程從隊列中擷取了
//隊列中存儲的元素
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue()); //DelayedWorkQueue中的元素隻有當其指定的延遲時間到了,才能夠從隊列中擷取到該元素。
//DelayedWorkQueue是一個沒有大小限制的隊列,是以往隊列中插入資料的操作(生産者)
//永遠不會被阻塞,而隻有擷取資料的操作(消費者)才會被阻塞
}
- threadFactory(可選):線程工廠指定建立線程的方式,需要實作 ThreadFactory 接口,并實作 newThread(Runnable r) 方法。該參數可以不用指定,Executors 架構已經為我們實作了一個預設的線程工廠
handler(可選):拒絕政策。當達到最大線程數時需要執行的拒絕政策。
ThreadPoolExecutor 已經為我們實作了 4 種拒絕政策:
AbortPolicy(預設):丢棄任務并抛出 RejectedExecutionException 異常。
CallerRunsPolicy:由調用線程處理該任務。
DiscardPolicy:丢棄任務,但是不抛出異常。可以配合這種模式進行自定義的處理方式。
DiscardOldestPolicy:丢棄隊列最早的未處理任務,然後重新嘗試執行任務。
Atomic 原⼦類
Atomic 是指⼀個操作是不可中斷的。即使是在多個線程⼀起執⾏的時候,⼀個操作⼀旦開始,就不會被其他線程⼲擾。
主要利⽤ CAS (compare and swap) + volatile 和 native ⽅法來保證原⼦操作
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe U = Unsafe.getUnsafe();
private volatile int value; // volatile 修飾, 保證變量線上程間的可見性
// objectFieldOffset(AtomicInteger.class, "value") :
// Reports the location of the field with a given name in the storage
// allocation of its class.
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
public AtomicInteger(int initialValue) {
value = initialValue;
}
// @param newValue the new value
// @return the previous value
public final int getAndSet(int newValue) {
return U.getAndSetInt(this, VALUE, newValue);
}
public final class Unsafe {
private static native void registerNatives();
static {
registerNatives();
}
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
public static Unsafe getUnsafe() {
return theUnsafe;
}
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset); // 一個 Native 方法
} while (!weakCompareAndSetInt(o, offset, v, newValue)); // CAS 方式
return v;
}
}
Threadocal變量
ThreadLocal變量是用來在多線程中實作資料隔離的,每個線程都有一個ThreadLocal變量的副本,填充的資料隻屬于目前線程,防止在多線程的環境中自己的變量被其他線程篡改。
底層原理
線程中使用ThreadLocal 變量的步驟
set(T value):線程設定該ThreadLocal對應的變量
get():線程擷取該ThreadLocal對應的變量
remove():線程移除該ThreadLocal
在set() 的時候,首先會擷取目前線程Thread類中定義的一個字段 threadLocals,
threadLocals 是一個ThreadLocalMap類型的字段,這個類型類似HashMap,底層結構是一個Entry<K,V>類型的數組
得到了threadLocals這個map結構之後,把自己當做key,調用threadLocals.set(key,value)。
使用場景有哪些?
Android 中的消息分發機制中,定義是一個線程隻能有一個Looper對象,利用的就是ThreadLocal變量來實作的Looper的線程唯一性(因為ThreadLocalMap底層是一個Entry<k,v>數組,是以一個線程一個key隻能有一個value,即Looper對象)
volatile, Atomic 總結
public class VolatileModel {
static int count = 0;
volatile static int count1 = 0;
static AtomicInteger count2 = new AtomicInteger(0);
public void increment() {
count ++;
count1 ++;
count2.incrementAndGet();
}
public class myRunable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0; i < 1000; i++)
increment();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
VolatileModel model = new VolatileModel();
Thread thread1 = new Thread(model.new myRunable());
Thread thread2 = new Thread(model.new myRunable());
thread1.start();
thread2.start();
while(Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(count);
System.out.println(count1);
System.out.println(count2);
}
}
method 0001 001F 000B 0001 Attributes {
code 000C 0000004E 0002 0001 00000018 Code {
getstatic B2 000D
iconst_1 04
iadd 60
putstatic B3 000D
getstatic B2 000F
iconst_1 04
iadd 60
putstatic B3 000F
getstatic B2 0017
invokevirtual B6 0020
pop 57
return B1
} 0000 ExceptionTable {
} 0002 Attributes {
lineNumberTable 0019 00000012 0004 Table {
lineNumber 0000 000C
lineNumber 0008 000D
lineNumber 0010 000E
lineNumber 0017 000F
}
localVariableTable 001A 0000000C 0001 Table {
localVariable 0000 0018 001D 001E 0000
}
}
}
當變量使用volatile 修飾時,count1的位元組碼的通路标志為ACC_STATIC, ACC_VOLATILE,在進行寫操作(putstatic )到主存(方法區 ) 的時候,JVM 會執行相應的處理,使得其他使用該變量的線程中的工作記憶體(操作數棧 )中的值無效,必須重新從主存中取值。
以後有時間再深入的了解JVM源碼是怎麼實作的吧ヽ(ー_ー)ノ
Java記憶體區域,Java記憶體模型,硬體記憶體模型
!!!方法區并不等于主記憶體,操作數棧也并不等于工作記憶體
Java 記憶體模型中的主存 可以類比硬體記憶體模型中的主存
Java 記憶體模型中的工作記憶體 可以類比硬體記憶體模型中的緩存
Java 記憶體模型中的JVM 可以類比硬體記憶體模型中的CPU
java-memory-model, Hardware Memory Architecture