1. volatile
1.1 volatile介紹
volatile
保證了共享變量的“可見性”去件∶咭可見性的意思是當一個線程修改一個共享變量時必孤,另外一個線程能讀到這個修改的值蓖救。它在某些情況下比synchronized
的開銷更小侣肄。
舉個例子我們來分析下面的代碼:
public class Main {
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
new Thread(volatileTest).start();
while (true) {
if (volatileTest.isFlag()){
System.out.println("over");
break;
}
}
}
}
class VolatileTest implements Runnable {
private boolean flag;
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag=" + flag);
}
public boolean isFlag() {
return flag;
}
}
上面的代碼最后輸出結(jié)果是:
flag=true
這個結(jié)果是令人詫異的旧困,程序會一直執(zhí)行while
循環(huán)不結(jié)束,flag
已經(jīng)為true
了稼锅,為什么while
循環(huán)還是不結(jié)束呢吼具?說明這里的flag
同時有了兩個值
- 在主線程中:
flag=false
- 在副線程中:
flag=true
變量實際是一段內(nèi)存空間,并不存在同時有兩種信息的狀態(tài)矩距。其實線程在操作主存中的變量數(shù)據(jù)時拗盒,首先會將數(shù)據(jù)復制到線程私有內(nèi)存中,當操作完成后才會將數(shù)據(jù)寫回主存锥债,當多個線程操作一個共享變量時陡蝇,由于線程的修改痊臭,導致數(shù)據(jù)不一致性。就發(fā)生了上述結(jié)果登夫。
為了解決共享變量的不一致性广匙,使得多線程對共享變量的修改的可見。上述結(jié)果我們可以使用synchronized
關(guān)鍵字來解決恼策。如下:
while (true) {
synchronized (volatileTest){
if (volatileTest.isFlag()){
System.out.println("over");
break;
}
}
}
當是這樣解決又有一個很大的問題鸦致,synchronized
是悲觀鎖,使得多線程堵塞等待戏蔑,極大的降低多線程的效率蹋凝。那有沒有一個更好的解決辦法呢?這里就可以用到volatile
關(guān)鍵字了总棵,修改如下:
private volatile boolean flag;
只需要將變量flag
聲明時鳍寂,用volatile
修飾就可以保證共享變量flag
的可見性。再次運行就不會發(fā)生堵塞數(shù)據(jù)不一致的問題了情龄。
注意
: 如果將代碼改成下面的迄汛,運行結(jié)果也是沒有問題的,導致上面的結(jié)果還有一個重要的原因骤视,while
循環(huán)中執(zhí)行的太快鞍爱,導致主線程來不及去主存中刷新數(shù)據(jù)。
while (true) {
// 只要是需要消耗一定的時間专酗,讓主線程能從主存讀取數(shù)據(jù)即可
System.out.println("no over");
if (volatileTest.isFlag()) {
System.out.println("over");
break;
}
}
1.2 volatile的三大特性:
- 可見性
- 不保證原子性
- 禁止指令重排
具體是如何做到的可以參考以下博客
《死磕Java——volatile的理解》
2. Atomic
jdk1.5
后java.util.concurrent.atomic
包下提供了常用的原子操作類睹逃,什么是原子操作呢?顧名思義祷肯,就是不可分割的操作沉填。
- i++的原子性問題:i++的操作實際上分為三個步驟
"讀-改-寫"
int i=10;
i=i++; //10
// 上面的代碼等同于下面的
int i = 10;
int temp=i;
i=i+1;
i=temp;
// 所以最后i的值為10
-
原子變量:jdk1.5后java.util.concurrent.atomic包下提供了常用的原子變量:
-
volatile
保證內(nèi)存可見性 -
CAS(Compare-And-Swap)
算法保證數(shù)據(jù)的原子性CAS
算法是硬件對于并發(fā)操作共享數(shù)據(jù)的支持
CAS
包含了三個操作數(shù):- 內(nèi)存值
V
- 預估值
A
- 更新值
B
當且僅當V==A
時,V = B
佑笋,否則將不做任何操作
- 內(nèi)存值
可參考博客:
《Java中atomic包中的原子操作類總結(jié)》
CAS的實現(xiàn)需要硬件指令集的支撐翼闹,在JDK1.5后虛擬機才可以使用處理器提供的CMPXCHG指令實現(xiàn)。
3. ConcurrentHashMap
3.1 ConcurrentHashMap 采用"鎖分段"機制
Java5.0
在java.util.concurrent
包中提供了多種并發(fā)容器類來改進同步容器的性能蒋纬。ConcurrentHashMap
同步容器類是Java5
增加的一個線程安全的哈希表猎荠。對與多線程的操作,介于HashMap
與Hashtable
之間蜀备。內(nèi)部采用“鎖分段”機制替代Hashtable
的獨占鎖关摇。進而提高性能。此包還提供了設(shè)計用于多線程上下文中的Collection
實現(xiàn):
ConcurrentHashMap
碾阁、ConcurrentSkipListMap
输虱、ConcurrentSkipListSet
、CopyOnWriteArrayList
和CopyOnWriteArrayset
瓷蛙。當期望許多線程訪問一個給定collection
時悼瓮,ConcurrentHashMap
通常優(yōu)于同步的HashMap
戈毒,ConcurrentSkipListMap
通常優(yōu)于同步的TreeMap
。當期望的讀數(shù)和遍歷遠遠大于列表的更新數(shù)時横堡,CopyOnWriteArrayList
優(yōu)于同步的ArrayList
4. CountDownLatch
4.1 CountDownLatch閉鎖
CountDownLatch
一個同步輔助類埋市,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個或多個線程一直等待命贴。
閉鎖可以延遲線程的進度直到其到達終止狀態(tài)道宅,閉鎖可以用來確保某些活動直到其他活動都完成才繼續(xù)執(zhí)行:
- 確保某個計算在其需要的所有資源都被初始化之后才繼續(xù)執(zhí)行;
- 確保某個服務在其依賴的所有其他服務都已經(jīng)啟動之后才啟動胸蛛;
- 等待直到某個操作所有參與者都準備就緒再繼續(xù)執(zhí)行污茵。
CountDownLatch使用實例代碼:
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
new Thread(ld).start();
}
latch.await();
long end = System.currentTimeMillis();
System.out.println("總時長為:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
double random = Math.random() * 100;
if (random > 99) {
System.out.println(random);
}
}
latch.countDown();
}
}
Callable
Callable介紹
Runnable
是執(zhí)行工作的獨立任務,但是它不返回任何值葬项。在Java SE5
中引入的Callable
是一種具有類型參數(shù)的泛型泞当,泛型類型是方法call()
的返回的值類型。
四種執(zhí)行線程方式的介紹
種數(shù) | 種類 | 說明 |
---|---|---|
1 | 實現(xiàn)Runnable 接口 |
通過Thread 實例啟動它 |
2 | 繼承Thread 類 |
重寫Thread 的run 方法 |
3 | 實現(xiàn)Callable 接口 |
通過FutureTask 包裝民珍,然后再通過Thread 啟動 |
4 | 實現(xiàn)Callable 接口 |
ExecutorServices.submit() |
可參考博客:
《徹底理解Java的Future模式》
《Future模式添加Callback及Promise 模式》
Lock
用于解決多線程安全問題的方式:
-
synchronized
:隱式鎖襟士、重量級- 同步代碼塊
- 同步方法
-
jdk 1.5
后,Lock
:輕量級- 同步鎖
Lock
注意:是一個顯示鎖嚷量,需要通過lock()
方法上鎖陋桂,必須通過unlock()
方法進行釋放鎖
- 同步鎖
多線程安全問題演示
買票案例代碼演示:
public class Main {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"一號售票窗口").start();
new Thread(ticket,"二號售票窗口").start();
new Thread(ticket,"三號售票窗口").start();
}
}
class Ticket implements Runnable {
private int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("賣出一張票,剩余還有:" + --num);
}else if (num == 0){
break;
}
}
}
}
上面的代碼存在線程安全問題 蝶溶,多線程下對同一共享變量進行修改嗜历。
用第一種保證安全性:
while (true) {
synchronized (this){
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("賣出一張票,剩余還有:" + --num);
}else if (num == 0){
break;
}
}
}
但是這樣效率嚴重降低抖所。
用第三種方式保證安全性:
while (true) {
lock.lock();
try{
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("賣出一張票梨州,剩余還有:" + --num);
} else if (num == 0) {
break;
}
}finally {
lock.unlock();
}
}
Condition
編寫一個程序,開啟
3
個線程部蛇,這三個線程的ID
分別為A摊唇、B咐蝇、C
涯鲁,每個線程將自己的ID
在屏幕上打印10
遍,要求輸出的結(jié)果必須按順序顯示有序。
如:ABCABCABC….
依次遞歸
public class Main {
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
test.LoopA();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
test.LoopB();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
test.LoopC();
}
},"C").start();
}
}
class Test {
private int id = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void LoopA() {
lock.lock();
try {
while (id != 1) {
condition1.await();
}
System.out.println("A");
id = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void LoopB() {
lock.lock();
try {
while (id != 2) {
condition2.await();
}
System.out.println("B");
id = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void LoopC() {
lock.lock();
try {
while (id != 3) {
condition3.await();
}
System.out.println("C");
id = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
ReadWriteLock 讀寫鎖
寫寫/讀寫
需要“互斥”
讀讀
不需要互斥
public class Main {
public static void main(String[] args) {
Test test = new Test();
for (int i = 0; i < 10; i++) {
int j = i;
new Thread(() -> {
test.write("" + j, new Random().nextInt(10));
}).start();
}
for (int i = 0; i < 100; i++) {
int j = i;
new Thread(() -> {
test.read("" + j);
}).start();
}
}
}
class Test {
private int id = 1;
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public void read(String name) {
rwl.readLock().lock();
try {
System.out.println(String.format("名字:%s抹腿,讀出的數(shù)據(jù):%d", name, id));
} finally {
rwl.readLock().unlock();
}
}
public void write(String name, int id) {
rwl.writeLock().lock();
try {
this.id = id;
System.out.println(String.format("我是寫鎖,名字:%s旭寿,改寫數(shù)據(jù)為:%d", name, id));
} finally {
rwl.writeLock().unlock();
}
}
}
線程八鎖
- 兩個普通同步方法警绩,兩個線程,標準打印盅称,打蛹缦椤后室?//one two
- 新增 Thread.sleep()給getone(),打踊旌荨岸霹?//one two
- 新增普通方法 getThree(),打咏取贡避?//three one two
- 兩個普通同步方法,兩個Number對象予弧,打庸伟伞?//two one
- 修改 getone()為靜態(tài)同步方法掖蛤,打由蹦怼?//two one
- 修改兩個方法均為靜態(tài)同步方法蚓庭,一個Number對象水醋?//one two
- 一個靜態(tài)同步方法,一個非靜態(tài)同步方法彪置,兩個Number對象拄踪?//two one
- 兩個靜態(tài)同步方法,兩個Number對象拳魁?//one two
線程八鎖的關(guān)鍵:
- 非靜態(tài)方法的鎖默認為this惶桐,靜態(tài)方法的鎖為對應的Class實例
- 某一個時刻內(nèi),只能有一個線程持有鎖潘懊,無論幾個方法姚糊。
線程池
線程池介紹
線程池:提供了一個線程隊列,隊列中保存著所有等待狀態(tài)的線程授舟。避免了創(chuàng)建與銷毀額外開銷救恨,提高了響應的速度。
線程池的體系結(jié)構(gòu):
java.util.concurrent.Executor:負責線程的使用與調(diào)度的根接口
|--**ExecutorService子接口:線程池的主要接口
|--ThreadPoolExecutor 線程池的實現(xiàn)類
|--ScheduledExecutorService 子接口:負責線程的調(diào)度
|--ScheduledThreadPoolExecutor:繼承 ThreadPoolExecutor释树,
實現(xiàn) ScheduledExecutorService
工具類:Executors
ExecutorService newFixedThreadPool():創(chuàng)建固定大小的線程池
ExecutorService newCachedThreadPool():緩存線程池肠槽,線程池的數(shù)量不固定,可以根據(jù)需求自動的更改數(shù)量奢啥。
ExecutorService newSingleThreadExecutor():創(chuàng)建單個線程池秸仙。線程池中只有一個線程
ScheduledExecutorService newScheduledThreadPool():創(chuàng)建固定大小的線程,可以狂遲或定時的執(zhí)行任務桩盲。