什么是線程安全?
當(dāng)一個(gè)線程在同一時(shí)刻共享同一個(gè)全局變量或靜態(tài)變量時(shí)添寺,可能會(huì)受到其他線程的干擾胯盯,導(dǎo)致數(shù)據(jù)有問題,這種現(xiàn)象就叫線程安全問題计露。
為什么有線程安全問題博脑?
當(dāng)多個(gè)線程同時(shí)共享憎乙,同一個(gè)全局變量或靜態(tài)變量,做寫的操作時(shí)叉趣,可能會(huì)發(fā)生數(shù)據(jù)沖突問題泞边,也就是線程安全問題,但是做讀操作時(shí)不會(huì)發(fā)生數(shù)據(jù)沖突問題疗杉。
線程安全解決辦法阵谚?
1、如何解決多線程之間線程安全問題烟具?
答:使用多線程之間同步synchronized或使用鎖(lock)
2椭蹄、為什么使用線程同步或使用鎖能解決線程安全問題呢?
答:將可能會(huì)發(fā)生數(shù)據(jù)沖突問題(線程不安全問題)净赴,只能讓當(dāng)前一個(gè)線程進(jìn)行執(zhí)行。代碼執(zhí)行完成后釋放鎖罩润,讓后才能讓其他線程進(jìn)行執(zhí)行玖翅。這樣的話就可以解決線程不安全問題。
3割以、什么是多線程之間同步金度?
答:當(dāng)多個(gè)線程共享同一個(gè)資源,不會(huì)受到其他線程的干擾。
同步代碼塊
1严沥、什么是同步代碼塊猜极?
答:就是將可能會(huì)發(fā)生線程安全問題的代碼,用synchronized給包括起來消玄。
synchronized(同一個(gè)數(shù)據(jù)) {
// 可能會(huì)發(fā)生線程沖突問題
}
synchronized(對象) {//這個(gè)對象可以為任意對象
// 需要被同步的代碼
}
對象如同鎖跟伏,持有鎖的線程可以在同步中執(zhí)行。
沒持有鎖的線程即使獲取CPU的執(zhí)行權(quán)翩瓜,也進(jìn)不去受扳。
同步的前提:
必須有兩個(gè)或者兩個(gè)以上的線程
必須是多個(gè)線程使用同一個(gè)鎖
必須保證同步中只能有一個(gè)線程在運(yùn)行
同步的好處:
解決了多線程的安全問題
同步的弊端:
多個(gè)線程需要判斷鎖,較為消耗資源兔跌、搶鎖的資源勘高。
同步函數(shù)
1、什么是同步函數(shù)坟桅?
答:在方法上修飾 synchronized 稱為同步函數(shù)
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
同步函數(shù)使用的是 this 鎖
證明方式:一個(gè)線程使用同步代碼塊(this明鎖)华望,另一個(gè)線程使用同步函數(shù)。如果兩個(gè)線程搶票不能實(shí)現(xiàn)同步仅乓,那么會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)誤赖舟。
package cn.icloudit;
class ThreadTrain2 implements Runnable {
private int count = 100;
public boolean flag = true;
private static Object oj = new Object();
@Override
public void run() {
if (flag) {
while (count > 0) {
synchronized (this) {
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
}
} else {
while (count > 0) {
sale();
}
}
}
public synchronized void sale() {
// 前提 多線程進(jìn)行使用、多個(gè)線程只能拿到一把鎖夸楣。
// 保證只能讓一個(gè)線程 在執(zhí)行 缺點(diǎn)效率降低
// synchronized (oj) {
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
// }
}
}
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
ThreadTrain2 threadTrain1 = new ThreadTrain2();
Thread t1 = new Thread(threadTrain1, "①號(hào)窗口");
Thread t2 = new Thread(threadTrain1, "②號(hào)窗口");
t1.start();
Thread.sleep(40);
threadTrain1.flag = false;
t2.start();
}
}
靜態(tài)同步函數(shù)
1建蹄、什么是靜態(tài)同步函數(shù)碌更?
答:方法上加上 static 關(guān)鍵字,使用 synchronized 關(guān)鍵字修飾洞慎,或者使用類 .class 文件痛单。
靜態(tài)的同步函數(shù)使用的鎖是 該函數(shù)所屬字節(jié)碼文件對象
可以用 getClass方法獲取,也可以用當(dāng)前 類名.class 表示劲腿。
synchronized (ThreadTrain.class) {
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
總結(jié):
synchronized 修飾方法使用鎖是當(dāng)前 this 鎖旭绒。
synchronized 修飾靜態(tài)方法使用鎖是當(dāng)前類的 字節(jié)碼文件。
多線程死鎖
什么是多線程死鎖焦人?
答:同步中嵌套同步挥吵,導(dǎo)致鎖無法釋放
package cn.icloudit;
class ThreadTrain6 implements Runnable {
// 這是貨票總票數(shù),多個(gè)線程會(huì)同時(shí)共享資源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
// 鎖(同步代碼塊)在什么時(shí)候釋放? 代碼執(zhí)行完花椭, 自動(dòng)釋放鎖.
// 如果flag為true 先拿到 obj鎖,在拿到this 鎖忽匈、 才能執(zhí)行。
// 如果flag為false先拿到this,在拿到obj鎖矿辽,才能執(zhí)行丹允。
// 死鎖解決辦法:不要在同步中嵌套同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "張票.");
trainCount--;
}
}
}
}
public class DeadlockThread {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定義 一個(gè)實(shí)例
Thread thread1 = new Thread(threadTrain, "一號(hào)窗口");
Thread thread2 = new Thread(threadTrain, "二號(hào)窗口");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
多線程的三大特性
原子性
即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷袋倔,要么就都不執(zhí)行雕蔽。
一個(gè)很經(jīng)典的例子就是銀行賬戶轉(zhuǎn)賬問題:
比如從賬戶A向賬戶B轉(zhuǎn)1000元,那么必然包括2個(gè)操作:從賬戶A減去1000元宾娜,往賬戶B加上1000元批狐。這2個(gè)操作必須要具備原子性才能保證不出現(xiàn)一些意外的問題。
我們操作數(shù)據(jù)也是如此前塔,比如i = i+1嚣艇;其中就包括,讀取i的值华弓,計(jì)算i髓废,寫入i。這行代碼在Java中是不具備原子性的该抒,則多線程運(yùn)行肯定會(huì)出問題慌洪,所以也需要我們使用同步和lock這些東西來確保這個(gè)特性了。
原子性其實(shí)就是保證數(shù)據(jù)一致凑保、線程安全一部分冈爹。
可見性
當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值欧引,其他線程能夠立即看得到修改的值频伤。
若兩個(gè)線程在不同的cpu,那么線程1改變了i的值還沒刷新到主存芝此,線程2又使用了i憋肖,那么這個(gè)i值肯定還是之前的因痛,線程1對變量的修改線程沒看到這就是可見性問題。
有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行岸更。
一般來說處理器為了提高程序運(yùn)行效率鸵膏,可能會(huì)對輸入代碼進(jìn)行優(yōu)化,它不保證程序中各個(gè)語句的執(zhí)行先后順序同代碼中的順序一致怎炊,但是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的谭企。如下:
int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a*a; //語句4
則因?yàn)橹嘏判颍€可能執(zhí)行順序?yàn)?2-1-3-4评肆,1-3-2-4
但絕不可能 2-1-4-3债查,因?yàn)檫@打破了依賴關(guān)系。
顯然重排序?qū)尉€程運(yùn)行是不會(huì)有任何問題瓜挽,而多線程就不一定了盹廷,所以我們在多線程編程時(shí)就得考慮這個(gè)問題了。
Java內(nèi)存模型
共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM)久橙,JMM決定一個(gè)線程對共享變量的寫入時(shí),能對另一個(gè)線程可見俄占。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中剥汤,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本排惨。本地內(nèi)存是JMM的一個(gè)抽象概念吭敢,并不真實(shí)存在。它涵蓋了緩存暮芭,寫緩沖區(qū)鹿驼,寄存器以及其他的硬件和編譯器優(yōu)化。
總結(jié):什么是Java內(nèi)存模型:java內(nèi)存模型簡稱jmm辕宏,定義了一個(gè)線程對另一個(gè)線程可見畜晰。共享變量存放在主內(nèi)存中,每個(gè)線程都有自己的本地內(nèi)存瑞筐,當(dāng)多個(gè)線程同時(shí)訪問一個(gè)數(shù)據(jù)的時(shí)候凄鼻,可能本地內(nèi)存沒有及時(shí)刷新到主內(nèi)存,所以就會(huì)發(fā)生線程安全問題聚假。
Volatile 關(guān)鍵字
什么是 Volatile 關(guān)鍵字块蚌?
答:Volatile 關(guān)鍵字的作用是變量在多個(gè)線程之間可見。
class ThreadVolatileDemo extends Thread {
public boolean flag = true;
@Override
public void run() {
System.out.println("開始執(zhí)行子線程....");
while (flag) {
}
System.out.println("線程停止");
}
public void setRuning(boolean flag) {
this.flag = flag;
}
}
public class ThreadVolatile {
public static void main(String[] args) throws InterruptedException {
ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
threadVolatileDemo.start();
Thread.sleep(3000);
threadVolatileDemo.setRuning(false);
System.out.println("flag 已經(jīng)設(shè)置成false");
Thread.sleep(1000);
System.out.println(threadVolatileDemo.flag);
}
}
已經(jīng)將結(jié)果設(shè)置為fasle為什么膘格?還一直在運(yùn)行呢峭范。
原因:線程之間是不可見的,讀取的是副本瘪贱,沒有及時(shí)讀取到主內(nèi)存結(jié)果纱控。
解決辦法:使用Volatile關(guān)鍵字將解決線程之間可見性, 強(qiáng)制線程每次讀取該值的時(shí)候都去“主內(nèi)存”中取值
Volatile非原子性
public class VolatileNoAtomic extends Thread {
private static volatile int count;
// private static AtomicInteger count = new AtomicInteger(0);
private static void addCount() {
for (int i = 0; i < 1000; i++) {
count++;
// count.incrementAndGet();
}
System.out.println(count);
}
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
for (int i = 0; i < 10; i++) {
arr[i] = new VolatileNoAtomic();
}
for (int i = 0; i < 10; i++) {
arr[i].start();
}
}
}
結(jié)果發(fā)現(xiàn) 數(shù)據(jù)不同步辆毡,因?yàn)閂olatile不用具備原子性。
AtomicInteger原子類
AtomicInteger是一個(gè)提供原子操作的Integer類甜害,通過線程安全的方式操作加減舶掖。
public class VolatileNoAtomic extends Thread {
static int count = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//等同于i++
atomicInteger.incrementAndGet();
}
System.out.println(count);
}
public static void main(String[] args) {
// 初始化10個(gè)線程
VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
// 創(chuàng)建
volatileNoAtomic[i] = new VolatileNoAtomic();
}
for (int i = 0; i < volatileNoAtomic.length; i++) {
volatileNoAtomic[i].start();
}
}
}
volatile與synchronized區(qū)別
僅靠volatile不能保證線程的安全性。(原子性)
volatile輕量級唾那,只能修飾變量访锻。synchronized重量級,還可修飾方法闹获;
volatile只能保證數(shù)據(jù)的可見性期犬,不能用來同步,因?yàn)槎鄠€(gè)線程并發(fā)訪問volatile修飾的變量不會(huì)阻塞避诽。
synchronized 不僅保證可見性龟虎,而且還保證原子性,因?yàn)樯陈挥蝎@得了鎖的線程才能進(jìn)入臨界區(qū)鲤妥,從而保證臨界區(qū)中的所有語句都全部執(zhí)行。多個(gè)線程爭搶synchronized鎖對象時(shí)拱雏,會(huì)出現(xiàn)阻塞棉安。
最后
感謝你看到這里,看完有什么的不懂的可以在評論區(qū)問我铸抑,覺得文章對你有幫助的話記得給我點(diǎn)個(gè)贊贡耽,每天都會(huì)分享java相關(guān)技術(shù)文章或行業(yè)資訊,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章鹊汛!