1.線程池
1.1 線程狀态介紹
當線程被建立并啟動以後,它既不是一啟動就進入了執行狀态,也不是一直處于執行狀态。線程對象在不同的時期有不同的狀态。那麼Java中的線程存在哪幾種狀态呢?Java中的線程
狀态被定義在了java.lang.Thread.State枚舉類中,State枚舉類的源碼如下:
public class Thread {
public enum State {
/* 建立 */
NEW ,
/* 可運作狀态 */
RUNNABLE ,
/* 阻塞狀态 */
BLOCKED ,
/* 無限等待狀态 */
WAITING ,
/* 計時等待 */
TIMED_WAITING ,
/* 終止 */
TERMINATED;
}
// 擷取目前線程的狀态
public State getState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
}
通過源碼我們可以看到Java中的線程存在6種狀态,每種線程狀态的含義如下
線程狀态 | 具體含義 |
---|---|
NEW | 一個尚未啟動的線程的狀态。也稱之為初始狀态、開始狀态。線程剛被建立,但是并未啟動。還沒調用start方法。MyThread t = new MyThread()隻有線程象,沒有線程特征。 |
RUNNABLE | 當我們調用線程對象的start方法,那麼此時線程對象進入了RUNNABLE狀态。那麼此時才是真正的在JVM程序中建立了一個線程,線程一經啟動并不是立即得到執行,線程的運作與否要聽令與CPU的排程,那麼我們把這個中間狀态稱之為可執行狀态(RUNNABLE)也就是說它具備執行的資格,但是并沒有真正的執行起來而是在等待CPU的度。 |
BLOCKED | 當一個線程試圖擷取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀态;當該線程持有鎖時,該線程将變成Runnable狀态。 |
WAITING | 一個正在等待的線程的狀态。也稱之為等待狀态。造成線程等待的原因有兩種,分别是調用Object.wait()、join()方法。處于等待狀态的線程,正在等待其他線程去執行一個特定的操作。例如:因為wait()而等待的線程正在等待另一個線程去調用notify()或notifyAll();一個因為join()而等待的線程正在等待另一個線程結束。 |
TIMED_WAITING | 一個在限定時間内等待的線程的狀态。也稱之為限時等待狀态。造成線程限時等待狀态的原因有三種,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED | 一個完全運作完成的線程的狀态。也稱之為終止狀态、結束狀态 |
各個狀态的轉換,如下圖所示:
1.2 線程池-基本原理
概述 :
提到池,大家應該能想到的就是水池。水池就是一個容器,在該容器中存儲了很多的水。那麼什麼是線程池呢?線程池也是可以看做成一個池子,在該池子中存儲很多個線程。
線程池存在的意義:
系統建立一個線程的成本是比較高的,因為它涉及到與作業系統互動,當程式中需要建立大量生存期很短暫的線程時,頻繁的建立和銷毀線程對系統的資源消耗有可能大于業務處理是對系
統資源的消耗,這樣就有點"舍本逐末"了。針對這一種情況,為了提高性能,我們就可以采用線程池。線程池在啟動的時,會建立大量空閑線程,當我們向線程池送出任務的時,線程池就
會啟動一個線程來執行該任務。等待任務執行完畢以後,線程并不會死亡,而是再次傳回到線程池中稱為空閑狀态。等待下一次任務的執行。
線程池的設計思路 :
- 準備一個任務容器
- 一次性啟動多個(2個)消費者線程
- 剛開始任務容器是空的,是以線程都在wait
- 直到一個外部線程向這個任務容器中扔了一個"任務",就會有一個消費者線程被喚醒
- 這個消費者線程取出"任務",并且執行這個任務,執行完畢後,繼續等待下一次任務的到來
1.3 線程池-Executors預設線程池
概述 : JDK對線程池也進行了相關的實作,在真實企業開發中我們也很少去自定義線程池,而是使用JDK中自帶的線程池。
我們可以使用Executors中所提供的靜态方法來建立線程池
static ExecutorService newCachedThreadPool() 建立一個預設的線程池
static newFixedThreadPool(int nThreads) 建立一個指定最多線程數量的線程池
代碼實作 :
package com.itheima.mythreadpool;
//static ExecutorService newCachedThreadPool() 建立一個預設的線程池
//static newFixedThreadPool(int nThreads) 建立一個指定最多線程數量的線程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
//1,建立一個預設的線程池對象.池子中預設是空的.預設最多可以容納int類型的最大值.
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors --- 可以幫助我們建立線程池對象
//ExecutorService --- 可以幫助我們控制線程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在執行了");
});
//Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在執行了");
});
executorService.shutdown();
}
}
1.4 線程池-Executors建立指定上限的線程池
使用Executors中所提供的靜态方法來建立線程池
static ExecutorService newFixedThreadPool(int nThreads) : 建立一個指定最多線程數量的線程池
代碼實作 :
package com.itheima.mythreadpool;
//static ExecutorService newFixedThreadPool(int nThreads)
//建立一個指定最多線程數量的線程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
//參數不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
System.out.println(pool.getPoolSize());//0
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在執行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在執行了");
});
System.out.println(pool.getPoolSize());//2
// executorService.shutdown();
}
}
1.5 線程池-ThreadPoolExecutor
建立線程池對象 :
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心線程數量,最大線程數量,空閑線程最大存活時間,任務隊列,建立線程工廠,任務的拒絕政策);
代碼實作 :
package com.itheima.mythreadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo3 {
// 參數一:核心線程數量
// 參數二:最大線程數
// 參數三:空閑線程最大存活時間
// 參數四:時間機關
// 參數五:任務隊列
// 參數六:建立線程工廠
// 參數七:任務的拒絕政策
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
}
1.6 線程池-參數詳解
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心線程的最大值,不能小于0
maximumPoolSize:最大線程數,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空閑線程最大存活時間,不能小于0
unit: 時間機關
workQueue: 任務隊列,不能為null
threadFactory: 建立線程工廠,不能為null
handler: 任務的拒絕政策,不能為null
1.7 線程池-非預設任務拒絕政策
RejectedExecutionHandler是jdk提供的一個任務拒絕政策接口,它下面存在4個子類。
ThreadPoolExecutor.AbortPolicy: 丢棄任務并抛出RejectedExecutionException異常。是預設的政策。
ThreadPoolExecutor.DiscardPolicy: 丢棄任務,但是不抛出異常 這是不推薦的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛棄隊列中等待最久的任務 然後把目前任務加入隊列中。
ThreadPoolExecutor.CallerRunsPolicy: 調用任務的run()方法繞過線程池直接執行。
注:明确線程池對多可執行的任務數 = 隊列容量 + 最大線程數
案例示範1:示範ThreadPoolExecutor.AbortPolicy任務處理政策
public class ThreadPoolExecutorDemo01 {
public static void main(String[] args) {
/**
* 核心線程數量為1 , 最大線程池數量為3, 任務容器的容量為1 ,空閑線程的最大存在時間為20s
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
// 送出5個任務,而該線程池最多可以處理4個任務,當我們使用AbortPolicy這個任務處理政策的時候,就會抛出異常
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 執行了任務");
});
}
}
}
控制台輸出結果
pool-1-thread-1---->> 執行了任務
pool-1-thread-3---->> 執行了任務
pool-1-thread-2---->> 執行了任務
pool-1-thread-3---->> 執行了任務
控制台報錯,僅僅執行了4個任務,有一個任務被丢棄了
案例示範2:示範ThreadPoolExecutor.DiscardPolicy任務處理政策
public class ThreadPoolExecutorDemo02 {
public static void main(String[] args) {
/**
* 核心線程數量為1 , 最大線程池數量為3, 任務容器的容量為1 ,空閑線程的最大存在時間為20s
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
// 送出5個任務,而該線程池最多可以處理4個任務,當我們使用DiscardPolicy這個任務處理政策的時候,控制台不會報錯
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 執行了任務");
});
}
}
}
控制台輸出結果
pool-1-thread-1---->> 執行了任務
pool-1-thread-1---->> 執行了任務
pool-1-thread-3---->> 執行了任務
pool-1-thread-2---->> 執行了任務
控制台沒有報錯,僅僅執行了4個任務,有一個任務被丢棄了
案例示範3:示範ThreadPoolExecutor.DiscardOldestPolicy任務處理政策
public class ThreadPoolExecutorDemo02 {
public static void main(String[] args) {
/**
* 核心線程數量為1 , 最大線程池數量為3, 任務容器的容量為1 ,空閑線程的最大存在時間為20s
*/
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
// 送出5個任務
for(int x = 0 ; x < 5 ; x++) {
// 定義一個變量,來指定指定目前執行的任務;這個變量需要被final修飾
final int y = x ;
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 執行了任務" + y);
});
}
}
}
控制台輸出結果
pool-1-thread-2---->> 執行了任務2
pool-1-thread-1---->> 執行了任務0
pool-1-thread-3---->> 執行了任務3
pool-1-thread-1---->> 執行了任務4
由于任務1線上程池中等待時間最長,是以任務1被丢棄。
案例示範4:示範ThreadPoolExecutor.CallerRunsPolicy任務處理政策
public class ThreadPoolExecutorDemo04 {
public static void main(String[] args) {
/**
* 核心線程數量為1 , 最大線程池數量為3, 任務容器的容量為1 ,空閑線程的最大存在時間為20s
*/
ThreadPoolExecutor threadPoolExecutor;
threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());
// 送出5個任務
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 執行了任務");
});
}
}
}
控制台輸出結果
pool-1-thread-1---->> 執行了任務
pool-1-thread-3---->> 執行了任務
pool-1-thread-2---->> 執行了任務
pool-1-thread-1---->> 執行了任務
main---->> 執行了任務
通過控制台的輸出,我們可以看到次政策沒有通過線程池中的線程執行任務,而是直接調用任務的run()方法繞過線程池直接執行。
2. 原子性
2.1 volatile-問題
代碼分析 :
package com.itheima.myvolatile;
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("小路同學");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("小皮同學");
t2.start();
}
}
package com.itheima.myvolatile;
public class Money {
public static int money = 100000;
}
package com.itheima.myvolatile;
public class MyThread1 extends Thread {
@Override
public void run() {
while(Money.money == 100000){
}
System.out.println("結婚基金已經不是十萬了");
}
}
package com.itheima.myvolatile;
public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money = 90000;
}
}
程式問題 : 女孩雖然知道結婚基金是十萬,但是當基金的餘額發生變化的時候,女孩無法知道最新的餘額。
2.2 volatile解決
以上案例出現的問題 :
當A線程修改了共享資料時,B線程沒有及時擷取到最新的值,如果還在使用原先的值,就會出現問題
1,堆記憶體是唯一的,每一個線程都有自己的線程棧。
2 ,每一個線程在使用堆裡面變量的時候,都會先拷貝一份到變量的副本中。
3 ,線上程中,每一次使用是從變量的副本中擷取的。
Volatile關鍵字 : 強制線程每次在使用的時候,都會看一下共享區域最新的值
代碼實作 : 使用volatile關鍵字解決
package com.itheima.myvolatile;
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("小路同學");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("小皮同學");
t2.start();
}
}
package com.itheima.myvolatile;
public class Money {
public static volatile int money = 100000;
}
package com.itheima.myvolatile;
public class MyThread1 extends Thread {
@Override
public void run() {
while(Money.money == 100000){
}
System.out.println("結婚基金已經不是十萬了");
}
}
package com.itheima.myvolatile;
public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money = 90000;
}
}
2.3 synchronized解決
synchronized解決 :
1 ,線程獲得鎖
2 ,清空變量副本
3 ,拷貝共享變量最新的值到變量副本中
4 ,執行代碼
5 ,将修改後變量副本中的值指派給共享資料
6 ,釋放鎖
代碼實作 :
package com.itheima.myvolatile2;
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("小路同學");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("小皮同學");
t2.start();
}
}
package com.itheima.myvolatile2;
public class Money {
public static Object lock = new Object();
public static volatile int money = 100000;
}
package com.itheima.myvolatile2;
public class MyThread1 extends Thread {
@Override
public void run() {
while(true){
synchronized (Money.lock){
if(Money.money != 100000){
System.out.println("結婚基金已經不是十萬了");
break;
}
}
}
}
}
package com.itheima.myvolatile2;
public class MyThread2 extends Thread {
@Override
public void run() {
synchronized (Money.lock) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money = 90000;
}
}
}
2.4 原子性
概述 : 所謂的原子性是指在一次操作或者多次操作中,要麼所有的操作全部都得到了執行并且不會受到任何因素的幹擾而中斷,要麼所有的操作都不執行,多個操作是一個不可以分割的整體。
代碼實作 :
package com.itheima.threadatom;
public class AtomDemo {
public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
}
class MyAtomThread implements Runnable {
private volatile int count = 0; //送冰淇淋的數量
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,從共享資料中讀取資料到本線程棧中.
//2,修改本線程棧中變量副本的值
//3,會把本線程棧中變量副本的值指派給共享資料.
count++;
System.out.println("已經送了" + count + "個冰淇淋");
}
}
}
代碼總結 : count++ 不是一個原子性操作, 他在執行的過程中,有可能被其他線程打斷
2.5 volatile關鍵字不能保證原子性
解決方案 : 我們可以給count++操作添加鎖,那麼count++操作就是臨界區中的代碼,臨界區中的代碼一次隻能被一個線程去執行,是以count++就變成了原子操作。
package com.itheima.threadatom2;
public class AtomDemo {
public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
}
class MyAtomThread implements Runnable {
private volatile int count = 0; //送冰淇淋的數量
private Object lock = new Object();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,從共享資料中讀取資料到本線程棧中.
//2,修改本線程棧中變量副本的值
//3,會把本線程棧中變量副本的值指派給共享資料.
synchronized (lock) {
count++;
System.out.println("已經送了" + count + "個冰淇淋");
}
}
}
}
2.6 原子性_AtomicInteger
概述:java從JDK1.5開始提供了java.util.concurrent.atomic包(簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單,性能高效,線程安全地更新一個變量的方式。因為變
量的類型有很多種,是以在Atomic包裡一共提供了13個類,屬于4種類型的原子更新方式,分别是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(字段)。本次我們隻講解
使用原子的方式更新基本類型,使用原子的方式更新基本類型Atomic包提供了以下3個類:
AtomicBoolean: 原子更新布爾類型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新長整型
以上3個類提供的方法幾乎一模一樣,是以本節僅以AtomicInteger為例進行講解,AtomicInteger的常用方法如下:
public AtomicInteger(): 初始化一個預設值為0的原子型Integer
public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer
int get(): 擷取值
int getAndIncrement(): 以原子方式将目前值加1,注意,這裡傳回的是自增前的值。
int incrementAndGet(): 以原子方式将目前值加1,注意,這裡傳回的是自增後的值。
int addAndGet(int data): 以原子方式将輸入的數值與執行個體中的值(AtomicInteger裡的value)相加,并傳回結果。
int getAndSet(int value): 以原子方式設定為newValue的值,并傳回舊值。
代碼實作 :
package com.itheima.threadatom3;
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomIntergerDemo1 {
// public AtomicInteger(): 初始化一個預設值為0的原子型Integer
// public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer
public static void main(String[] args) {
AtomicInteger ac = new AtomicInteger();
System.out.println(ac);
AtomicInteger ac2 = new AtomicInteger(10);
System.out.println(ac2);
}
}
package com.itheima.threadatom3;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomIntergerDemo2 {
// int get(): 擷取值
// int getAndIncrement(): 以原子方式将目前值加1,注意,這裡傳回的是自增前的值。
// int incrementAndGet(): 以原子方式将目前值加1,注意,這裡傳回的是自增後的值。
// int addAndGet(int data): 以原子方式将參數與對象中的值相加,并傳回結果。
// int getAndSet(int value): 以原子方式設定為newValue的值,并傳回舊值。
public static void main(String[] args) {
// AtomicInteger ac1 = new AtomicInteger(10);
// System.out.println(ac1.get());
// AtomicInteger ac2 = new AtomicInteger(10);
// int andIncrement = ac2.getAndIncrement();
// System.out.println(andIncrement);
// System.out.println(ac2.get());
// AtomicInteger ac3 = new AtomicInteger(10);
// int i = ac3.incrementAndGet();
// System.out.println(i);//自增後的值
// System.out.println(ac3.get());
// AtomicInteger ac4 = new AtomicInteger(10);
// int i = ac4.addAndGet(20);
// System.out.println(i);
// System.out.println(ac4.get());
AtomicInteger ac5 = new AtomicInteger(100);
int andSet = ac5.getAndSet(20);
System.out.println(andSet);
System.out.println(ac5.get());
}
}
2.7 AtomicInteger-記憶體解析
AtomicInteger原理 : 自旋鎖 + CAS 算法
CAS算法:
有3個操作數(記憶體值V, 舊的預期值A,要修改的值B)
當舊的預期值A == 記憶體值 此時修改成功,将V改為B
當舊的預期值A!=記憶體值 此時修改失敗,不做任何操作
并重新擷取現在的最新值(這個重新擷取的動作就是自旋)
2.8 AtomicInteger-源碼解析
代碼實作 :
package com.itheima.threadatom4;
public class AtomDemo {
public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
}
package com.itheima.threadatom4;
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomThread implements Runnable {
//private volatile int count = 0; //送冰淇淋的數量
//private Object lock = new Object();
AtomicInteger ac = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,從共享資料中讀取資料到本線程棧中.
//2,修改本線程棧中變量副本的值
//3,會把本線程棧中變量副本的值指派給共享資料.
//synchronized (lock) {
// count++;
// ac++;
int count = ac.incrementAndGet();
System.out.println("已經送了" + count + "個冰淇淋");
// }
}
}
}
源碼解析 :
//先自增,然後擷取自增後的結果
public final int incrementAndGet() {
//+ 1 自增後的結果
//this 就表示目前的atomicInteger(值)
//1 自增一次
return U.getAndAddInt(this, VALUE, 1) + 1;
}
public final int getAndAddInt(Object o, long offset, int delta) {
//v 舊值
int v;
//自旋的過程
do {
//不斷的擷取舊值
v = getIntVolatile(o, offset);
//如果這個方法的傳回值為false,那麼繼續自旋
//如果這個方法的傳回值為true,那麼自旋結束
//o 表示的就是記憶體值
//v 舊值
//v + delta 修改後的值
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
//作用:比較記憶體中的值,舊值是否相等,如果相等就把修改後的值寫到記憶體中,傳回true。表示修改成功。
// 如果不相等,無法把修改後的值寫到記憶體中,傳回false。表示修改失敗。
//如果修改失敗,那麼繼續自旋。
return v;
}
2.9 悲觀鎖和樂觀鎖
synchronized和CAS的差別 :
**相同點:**在多線程情況下,都可以保證共享資料的安全性。
**不同點:**synchronized總是從最壞的角度出發,認為每次擷取資料的時候,别人都有可能修改。是以在每 次操作共享資料之前,都會上鎖。(悲觀鎖)
cas是從樂觀的角度出發,假設每次擷取資料别人都不會修改,是以不會上鎖。隻不過在修改共享資料的時候,會檢查一下,别人有沒有修改過這個資料。
如果别人修改過,那麼我再次擷取現在最新的值。
如果别人沒有修改過,那麼我現在直接修改共享資料的值.(樂觀鎖)
3. 并發工具類
3.1 并發工具類-Hashtable
Hashtable出現的原因 : 在集合類中HashMap是比較常用的集合對象,但是HashMap是線程不安全的(多線程環境下可能會存在問題)。為了保證資料的安全性我們可以使用Hashtable,但是Hashtable的效率低下。
代碼實作 :
package com.itheima.mymap;
import java.util.HashMap;
import java.util.Hashtable;
public class MyHashtableDemo {
public static void main(String[] args) throws InterruptedException {
Hashtable<String, String> hm = new Hashtable<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 25; i++) {
hm.put(i + "", i + "");
}
});
Thread t2 = new Thread(() -> {
for (int i = 25; i < 51; i++) {
hm.put(i + "", i + "");
}
});
t1.start();
t2.start();
System.out.println("----------------------------");
//為了t1和t2能把資料全部添加完畢
Thread.sleep(1000);
//0-0 1-1 ..... 50- 50
for (int i = 0; i < 51; i++) {
System.out.println(hm.get(i + ""));
}//0 1 2 3 .... 50
}
}
3.2 并發工具類-ConcurrentHashMap基本使用
ConcurrentHashMap出現的原因 : 在集合類中HashMap是比較常用的集合對象,但是HashMap是線程不安全的(多線程環境下可能會存在問題)。為了保證資料的安全性我們可以使用Hashtable,但是Hashtable的效率低下。
基于以上兩個原因我們可以使用JDK1.5以後所提供的ConcurrentHashMap。
體系結構 :
總結 :
1 ,HashMap是線程不安全的。多線程環境下會有資料安全問題
2 ,Hashtable是線程安全的,但是會将整張表鎖起來,效率低下
3,ConcurrentHashMap也是線程安全的,效率較高。 在JDK7和JDK8中,底層原理不一樣。
代碼實作 :
package com.itheima.mymap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
public class MyConcurrentHashMapDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 25; i++) {
hm.put(i + "", i + "");
}
});
Thread t2 = new Thread(() -> {
for (int i = 25; i < 51; i++) {
hm.put(i + "", i + "");
}
});
t1.start();
t2.start();
System.out.println("----------------------------");
//為了t1和t2能把資料全部添加完畢
Thread.sleep(1000);
//0-0 1-1 ..... 50- 50
for (int i = 0; i < 51; i++) {
System.out.println(hm.get(i + ""));
}//0 1 2 3 .... 50
}
}
3.3 并發工具類-ConcurrentHashMap1.7原理
3.4 并發工具類-ConcurrentHashMap1.8原理
總結 :
1,如果使用空參構造建立ConcurrentHashMap對象,則什麼事情都不做。 在第一次添加元素的時候建立哈希表
2,計算目前元素應存入的索引。
3,如果該索引位置為null,則利用cas算法,将本結點添加到數組中。
4,如果該索引位置不為null,則利用volatile關鍵字獲得目前位置最新的結點位址,挂在他下面,變成連結清單。
5,當連結清單的長度大于等于8時,自動轉換成紅黑樹6,以連結清單或者紅黑樹頭結點為鎖對象,配合悲觀鎖保證多線程操作集合時資料的安全性
3.5 并發工具類-CountDownLatch
CountDownLatch類 :
方法 | 解釋 |
---|---|
public CountDownLatch(int count) | 參數傳遞線程數,表示等待線程數量 |
public void await() | 讓線程等待 |
public void countDown() | 目前線程執行完畢 |
使用場景: 讓某一條線程等待其他線程執行完畢之後再執行
代碼實作 :
package com.itheima.mycountdownlatch;
import java.util.concurrent.CountDownLatch;
public class ChileThread1 extends Thread {
private CountDownLatch countDownLatch;
public ChileThread1(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.吃餃子
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "在吃第" + i + "個餃子");
}
//2.吃完說一聲
//每一次countDown方法的時候,就讓計數器-1
countDownLatch.countDown();
}
}
package com.itheima.mycountdownlatch;
import java.util.concurrent.CountDownLatch;
public class ChileThread2 extends Thread {
private CountDownLatch countDownLatch;
public ChileThread2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.吃餃子
for (int i = 1; i <= 15; i++) {
System.out.println(getName() + "在吃第" + i + "個餃子");
}
//2.吃完說一聲
//每一次countDown方法的時候,就讓計數器-1
countDownLatch.countDown();
}
}
package com.itheima.mycountdownlatch;
import java.util.concurrent.CountDownLatch;
public class ChileThread3 extends Thread {
private CountDownLatch countDownLatch;
public ChileThread3(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.吃餃子
for (int i = 1; i <= 20; i++) {
System.out.println(getName() + "在吃第" + i + "個餃子");
}
//2.吃完說一聲
//每一次countDown方法的時候,就讓計數器-1
countDownLatch.countDown();
}
}
package com.itheima.mycountdownlatch;
import java.util.concurrent.CountDownLatch;
public class MotherThread extends Thread {
private CountDownLatch countDownLatch;
public MotherThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.等待
try {
//當計數器變成0的時候,會自動喚醒這裡等待的線程。
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.收拾碗筷
System.out.println("媽媽在收拾碗筷");
}
}
package com.itheima.mycountdownlatch;
import java.util.concurrent.CountDownLatch;
public class MyCountDownLatchDemo {
public static void main(String[] args) {
//1.建立CountDownLatch的對象,需要傳遞給四個線程。
//在底層就定義了一個計數器,此時計數器的值就是3
CountDownLatch countDownLatch = new CountDownLatch(3);
//2.建立四個線程對象并開啟他們。
MotherThread motherThread = new MotherThread(countDownLatch);
motherThread.start();
ChileThread1 t1 = new ChileThread1(countDownLatch);
t1.setName("小明");
ChileThread2 t2 = new ChileThread2(countDownLatch);
t2.setName("小紅");
ChileThread3 t3 = new ChileThread3(countDownLatch);
t3.setName("小剛");
t1.start();
t2.start();
t3.start();
}
}
總結 :
1. CountDownLatch(int count):參數寫等待線程的數量。并定義了一個計數器。
2. await():讓線程等待,當計數器為0時,會喚醒等待的線程
3. countDown(): 線程執行完畢時調用,會将計數器-1。
3.6 并發工具類-Semaphore
使用場景 :
可以控制通路特定資源的線程數量。
實作步驟 :
1,需要有人管理這個通道
2,當有車進來了,發通行許可證
3,當車出去了,收回通行許可證
4,如果通行許可證發完了,那麼其他車輛隻能等着
代碼實作 :
package com.itheima.mysemaphore;
import java.util.concurrent.Semaphore;
public class MyRunnable implements Runnable {
//1.獲得管理者對象,
private Semaphore semaphore = new Semaphore(2);
@Override
public void run() {
//2.獲得通行證
try {
semaphore.acquire();
//3.開始行駛
System.out.println("獲得了通行證開始行駛");
Thread.sleep(2000);
System.out.println("歸還通行證");
//4.歸還通行證
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.itheima.mysemaphore;
public class MySemaphoreDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
for (int i = 0; i < 100; i++) {
new Thread(mr).start();
}
}
}