1 - 線程
1.1 - 進(jìn)程
進(jìn)程就是正在運(yùn)行中的程序(進(jìn)程是駐留在內(nèi)存中的)
- 是系統(tǒng)執(zhí)行資源分配和調(diào)度的獨(dú)立單位
- 每一進(jìn)程都有屬于自己的存儲(chǔ)空間和系統(tǒng)資源
- 注意:進(jìn)程A和進(jìn)程B的內(nèi)存獨(dú)立不共享箫章。
1.2 - 線程
線程就是進(jìn)程中的單個(gè)順序控制流硼一,也可以理解成是一條執(zhí)行路徑
單線程:一個(gè)進(jìn)程中包含一個(gè)順序控制流(一條執(zhí)行路徑)
多線程:一個(gè)進(jìn)程中包含多個(gè)順序控制流(多條執(zhí)行路徑)
在java語言中:
線程A和線程B椎麦,堆內(nèi)存和方法區(qū)內(nèi)存共享。
但是棧內(nèi)存獨(dú)立,一個(gè)線程一個(gè)棧。假設(shè)啟動(dòng)10個(gè)線程,會(huì)有10個(gè)棧空間滔悉,每個(gè)棧和每個(gè)棧之間,互不干擾单绑,各自執(zhí)行各自的回官,這就是多線程并發(fā)。
java中之所以有多線程機(jī)制搂橙,目的就是為了提高程序的處理效率歉提。
對(duì)于單核的CPU來說,不能夠做到真正的多線程并發(fā)区转,但是可以做到給人一種“多線程并發(fā)”的感覺苔巨。對(duì)于單核的CPU來說,在某一個(gè)時(shí)間點(diǎn)上實(shí)際上只能處理一件事情废离,但是由于CPU的處理速度極快侄泽,多個(gè)線程之間頻繁切換執(zhí)行,跟人來的感覺是多個(gè)事情同時(shí)在做蜻韭。
1.3 -java中多線程的實(shí)現(xiàn)原理
就緒狀態(tài):就緒狀態(tài)的線程又叫做可運(yùn)行狀態(tài)悼尾,表示當(dāng)前線程具有搶奪CPU時(shí)間片的權(quán)力(CPU時(shí)間片就是執(zhí)行權(quán))。當(dāng)一個(gè)線程搶奪到CPU時(shí)間片之后肖方,就開始執(zhí)行run方法闺魏,run方法的開始執(zhí)行標(biāo)志著線程進(jìn)入運(yùn)行狀態(tài)。
運(yùn)行狀態(tài):run方法的開始執(zhí)行標(biāo)志著這個(gè)線程進(jìn)入運(yùn)行狀態(tài)俯画,當(dāng)之前占有的CPU時(shí)間片用完之后析桥,會(huì)重新回到就緒狀態(tài)繼續(xù)搶奪CPU時(shí)間片,當(dāng)再次搶到CPU時(shí)間之后,會(huì)重新進(jìn)入run方法接著上一次的代碼繼續(xù)往下執(zhí)行泡仗。
阻塞狀態(tài):當(dāng)一個(gè)線程遇到阻塞事件埋虹,例如接收用戶鍵盤輸入,或者sleep方法等沮焕,此時(shí)線程會(huì)進(jìn)入阻塞狀態(tài)吨岭,阻塞狀態(tài)的線程會(huì)放棄之前占有的CPU時(shí)間片拉宗。之前的時(shí)間片沒了需要再次回到就緒狀態(tài)搶奪CPU時(shí)間片峦树。
鎖池:在這里找共享對(duì)象的對(duì)象鎖線程進(jìn)入鎖池找共享對(duì)象的對(duì)象鎖的時(shí)候,會(huì)釋放之前占有CPU時(shí)間片旦事,有可能找到了魁巩,有可能沒找到,沒找到則在鎖池中等待姐浮,如果找到了會(huì)進(jìn)入就緒狀態(tài)繼續(xù)搶奪CPU時(shí)間片谷遂。(這個(gè)進(jìn)入鎖池,可以理解為一種阻塞狀態(tài))
1.4 - 多線程的實(shí)現(xiàn)方式(一)
- 繼承Thread類
1卖鲤、自定義一個(gè)類MyThread類肾扰,用來繼承與Thread類
2、在MyThread類中重寫run()方法
3蛋逾、在測試類中創(chuàng)建MyThread類的對(duì)象
4集晚、啟動(dòng)線程
/**
* @author Mr.樂
* @Description
*/
public class Demo01 {
public static void main(String[] args) {
//創(chuàng)建線程
MyThread t01 = new MyThread();
MyThread t02 = new MyThread();
MyThread t03 = new MyThread("線程03");
//開啟線程
// t01.run();
// t02.run();
// t03.run();
// 不會(huì)啟動(dòng)線程,不會(huì)分配新的分支棧区匣。(這種方式就是單線程偷拔。)
// start()方法的作用是:啟動(dòng)一個(gè)分支線程,在JVM中開辟一個(gè)新的椏鞴常空間莲绰,這段代碼任務(wù)完成之后,瞬間就結(jié)束了姑丑。
// 這段代碼的任務(wù)只是為了開啟一個(gè)新的椄蚯空間,只要新的椪ぐВ空間開出來震肮,start()方法就結(jié)束了。線程就啟動(dòng)成功了昌屉。
// 啟動(dòng)成功的線程會(huì)自動(dòng)調(diào)用run方法钙蒙,并且run方法在分支棧的棧底部(壓棧)。
// run方法在分支棧的棧底部间驮,main方法在主棧的棧底部躬厌。run和main是平級(jí)的。
t01.start();
t02.start();
t03.start();
//設(shè)置線程名(補(bǔ)救的設(shè)置線程名的方式)
t01.setName("線程01");
t02.setName("線程02");
//設(shè)置主線程名稱
Thread.currentThread().setName("主線程");
for (int i = 0; i < 50; i++) {
//Thread.currentThread() 獲取當(dāng)前正在執(zhí)行線程的對(duì)象
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
//run方法是每個(gè)線程運(yùn)行過程中都必須執(zhí)行的方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
此處最重要的為start()方法。單純調(diào)用run()方法不會(huì)啟動(dòng)線程扛施,不會(huì)分配新的分支棧鸿捧。
start()方法的作用是:啟動(dòng)一個(gè)分支線程,在JVM中開辟一個(gè)新的椄碓空間匙奴,這段代碼任務(wù)完成之后,瞬間就結(jié)束了妄荔。線程就啟動(dòng)成功了泼菌。
啟動(dòng)成功的線程會(huì)自動(dòng)調(diào)用run方法(由JVM線程調(diào)度機(jī)制來運(yùn)作的),并且run方法在分支棧的棧底部(壓棧)啦租。
run方法在分支棧的棧底部哗伯,main方法在主棧的棧底部。run和main是平級(jí)的篷角。
單純使用run()方法是不能多線程并發(fā)的焊刹。
1.5 - 設(shè)置和獲取線程名
設(shè)置線程名
setName(String name):
設(shè)置線程名
通過帶參構(gòu)造方法設(shè)置線程名獲取線程名
getName():
返回字符串形式的線程名
Thread.CurrentThread():
返回當(dāng)前正在執(zhí)行的線程對(duì)象
1.6 - 多線程的實(shí)現(xiàn)方式(二)
- 實(shí)現(xiàn)Runnable接口
1、自定義一個(gè)MyRunnable類來實(shí)現(xiàn)Runnable接口
2恳蹲、在MyRunnable類中重寫run()方法
3虐块、創(chuàng)建Thread對(duì)象,并把MyRunnable對(duì)象作為Tread類構(gòu)造方法的參數(shù)傳遞進(jìn)去
4嘉蕾、啟動(dòng)線程
/**
* @author Mr.樂
* @Description
*/
public class Demo02 {
public static void main(String[] args) {
MyRunnable myRun = new MyRunnable();//將一個(gè)任務(wù)提取出來贺奠,讓多個(gè)線程共同去執(zhí)行
//封裝線程對(duì)象
Thread t01 = new Thread(myRun, "線程01");
Thread t02 = new Thread(myRun, "線程02");
Thread t03 = new Thread(myRun, "線程03");
//開啟線程
t01.start();
t02.start();
t03.start();
//通過匿名內(nèi)部類的方式創(chuàng)建線程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
},"線程04").start();
}
}
//自定義線程類,實(shí)現(xiàn)Runnable接口
//這并不是一個(gè)線程類荆针,是一個(gè)可運(yùn)行的類敞嗡,它還不是一個(gè)線程。
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
1.7 - 多線程的實(shí)現(xiàn)方式(三)
-
實(shí)現(xiàn)Callable接口( java.util.concurrent.FutureTask; /JUC包下的航背,屬于java的并發(fā)包喉悴,老JDK中沒有這個(gè)包。新特性玖媚。)
1箕肃、自定義一個(gè)MyCallable類來實(shí)現(xiàn)Callable接口
2、在MyCallable類中重寫call()方法
3今魔、創(chuàng)建FutureTask勺像,Thread對(duì)象,并把MyCallable對(duì)象作為FutureTask類構(gòu)造方法的參數(shù)傳遞進(jìn)去错森,把FutureTask對(duì)象傳遞給Thread對(duì)象吟宦。
4、啟動(dòng)線程這種方式的優(yōu)點(diǎn):可以獲取到線程的執(zhí)行結(jié)果涩维。 這種方式的缺點(diǎn):效率比較低殃姓,在獲取t線程執(zhí)行結(jié)果的時(shí)候,當(dāng)前線程受阻塞,效率較低蜗侈。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author Mr.樂
* @Description 線程實(shí)現(xiàn)的第三種方式
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
// 第一步:創(chuàng)建一個(gè)“未來任務(wù)類”對(duì)象篷牌。
// 參數(shù)非常重要,需要給一個(gè)Callable接口實(shí)現(xiàn)類對(duì)象踏幻。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相當(dāng)于run方法枷颊。只不過這個(gè)有返回值
// 線程執(zhí)行一個(gè)任務(wù),執(zhí)行之后可能會(huì)有一個(gè)執(zhí)行結(jié)果
// 模擬執(zhí)行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自動(dòng)裝箱(300結(jié)果變成Integer)
}
});
// 創(chuàng)建線程對(duì)象
Thread t = new Thread(task);
// 啟動(dòng)線程
t.start();
// 這里是main方法该面,這是在主線程中夭苗。
// 在主線程中,怎么獲取t線程的返回結(jié)果吆倦?
// get()方法的執(zhí)行會(huì)導(dǎo)致“當(dāng)前線程阻塞”
Object obj = task.get();
System.out.println("線程執(zhí)行結(jié)果:" + obj);
// main方法這里的程序要想執(zhí)行必須等待get()方法的結(jié)束
// 而get()方法可能需要很久听诸。因?yàn)間et()方法是為了拿另一個(gè)線程的執(zhí)行結(jié)果
// 另一個(gè)線程執(zhí)行是需要時(shí)間的。
System.out.println("hello world!");
}
}
1.8 -線程控制
方法名 | 說明 |
---|---|
void yield() | 使當(dāng)前線程讓步蚕泽,重新回到爭奪CPU執(zhí)行權(quán)的隊(duì)列中 |
static void sleep(long ms) | 使當(dāng)前正在執(zhí)行的線程停留指定的毫秒數(shù) |
void join() | 等死(等待當(dāng)前線程銷毀后,再繼續(xù)執(zhí)行其它的線程) |
void interrupt() | 終止線程睡眠 |
1.8.1 -sleep()方法 (誰執(zhí)行誰就是當(dāng)前線程)
/**
* @author Mr.樂
* @Description 線程睡眠
*/
public class DemoSleep {
public static void main(String[] args) {
// 創(chuàng)建線程
MyThread1 t01 = new MyThread1("黃固");
MyThread1 t02 = new MyThread1("歐陽鋒");
MyThread1 t03 = new MyThread1("段智興");
MyThread1 t04 = new MyThread1("洪七公");
//開啟線程
t01.start();
t02.start();
t03.start();
t04.start();
}
}
class MyThread1 extends Thread{
public MyThread1() {
}
public MyThread1(String name) {
super(name);
}
@Override
// 重點(diǎn):run()當(dāng)中的異常不能throws桥嗤,只能try catch
// 因?yàn)閞un()方法在父類中沒有拋出任何異常须妻,子類不能比父類拋出更多的異常。
public void run() {
for (int i = 1; i < 50; i++) {
System.out.println(this.getName() + "正在打出第 - " + i + "招");
try {
Thread.sleep(500);//讓當(dāng)前正在執(zhí)行的線程睡眠指定毫秒數(shù)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:run()方法中的異常只能try catch泛领,因?yàn)楦割悰]有拋出異常荒吏,子類不能拋出比父類更多的異常。
1.8.2 -interrupt()方法和stop()方法
/**
* @author Mr.樂
* @Description 終止線程
*/
public class DemoInterrupt {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 終斷t線程的睡眠(這種終斷睡眠的方式依靠了java的異常處理機(jī)制渊鞋。)
t.interrupt();
// t.stop(); //強(qiáng)行終止線程
//缺點(diǎn):容易損壞數(shù)據(jù) 線程沒有保存的數(shù)據(jù)容易丟失
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---> begin");
try {
// 睡眠1年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
// e.printStackTrace();
}
//1年之后才會(huì)執(zhí)行這里
System.out.println(Thread.currentThread().getName() + "---> end");
}
}
1.8.3 -合理的終止線程
做一個(gè)boolean類型的標(biāo)記
/**
* @author Mr.樂
* @Description
*/
public class DemoSleep02 {
public static void main(String[] args) {
MyRunable4 r = new MyRunable4();
Thread t = new Thread(r);
t.setName("t");
t.start();
// 模擬5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 終止線程
// 你想要什么時(shí)候終止t的執(zhí)行绰更,那么你把標(biāo)記修改為false,就結(jié)束了锡宋。
r.run = false;
}
}
class MyRunable4 implements Runnable {
// 打一個(gè)布爾標(biāo)記
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++){
if(run){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// return就結(jié)束了儡湾,你在結(jié)束之前還有什么沒保存的。
// 在這里可以保存呀执俩。
//save....
//終止當(dāng)前線程
return;
}
}
}
}
1.8.4 - yield()
暫停當(dāng)前正在執(zhí)行的線程對(duì)象徐钠,并執(zhí)行其他線程
yield()方法不是阻塞方法。讓當(dāng)前線程讓位役首,讓給其它線程使用尝丐。
yield()方法的執(zhí)行會(huì)讓當(dāng)前線程從“運(yùn)行狀態(tài)”回到“就緒狀態(tài)”。
注意:在回到就緒之后衡奥,有可能還會(huì)再次搶到爹袁。
/**
* @author Mr.樂
* @Description 線程讓位
*/
public class DemoYield {
public static void main(String[] args) {
//創(chuàng)建線程
MyThread5 t01 = new MyThread5("線程01");
MyThread5 t02 = new MyThread5("線程02");
MyThread5 t03 = new MyThread5("線程03");
//開啟線程
t01.start();
t02.start();
t03.start();
}
}
class MyThread5 extends Thread{
public MyThread5() {
}
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(30 == i){
Thread.yield();//當(dāng)循i環(huán)到30時(shí),讓線程讓步
//1矮固、回到搶占隊(duì)列中失息,又爭奪到了執(zhí)行權(quán)
//2、回到搶占隊(duì)列中,沒有爭奪到執(zhí)行權(quán)
}
System.out.println(this.getName() + ":" + i);
}
}
}
1.8.5 -join()
1.9 - 線程的調(diào)度
- 線程調(diào)度模型
- 均分式調(diào)度模型:所有的線程輪流使用CPU的使用權(quán)根时,平均分配給每一個(gè)線程占用CPU的時(shí)間瘦赫。
- 搶占式調(diào)度模型:優(yōu)先讓優(yōu)先級(jí)高的線程使用CPU,如果線程的優(yōu)先級(jí)相同蛤迎,那么就會(huì)隨機(jī)選擇一個(gè)線程來執(zhí)行确虱,優(yōu)先級(jí)高的占用CPU時(shí)間相對(duì)來說會(huì)高一點(diǎn)點(diǎn)。
Java中JVM使用的就是搶占式調(diào)度模型
getPriority():
獲取線程優(yōu)先級(jí)
setPriority:
設(shè)置線程優(yōu)先級(jí)
/**
* @author Mr.樂
* @Description 線程的調(diào)度
*/
public class Demo07 {
public static void main(String[] args) {
//創(chuàng)建線程
MyThread t01 = new MyThread("線程01");
MyThread t02 = new MyThread("線程02");
MyThread t03 = new MyThread("線程03");
//獲取線程優(yōu)先級(jí)替裆,默認(rèn)是5
// System.out.println(t01.getPriority());
// System.out.println(t02.getPriority());
// System.out.println(t03.getPriority());
//設(shè)置線程優(yōu)先級(jí)
t01.setPriority(Thread.MIN_PRIORITY); //低 - 理論上來講校辩,最后完成
t02.setPriority(Thread.NORM_PRIORITY); //中
t03.setPriority(Thread.MAX_PRIORITY); //高 - 理論上來講,最先完成
//開啟線程
t01.start();
t02.start();
t03.start();
}
}
2 - 線程的安全
2.1 - 數(shù)據(jù)安全問題
- 是否具備多線程的環(huán)境
- 是否有共享數(shù)據(jù)
- 是否有多條語句操作共享數(shù)據(jù)
- 例如:我和小明同時(shí)取一個(gè)賬戶的錢辆童,我取錢后數(shù)據(jù)還沒返回給服務(wù)器宜咒,小明又取了,這個(gè)時(shí)候小明的余額還是原來的把鉴。
- 如何解決故黑?線程排隊(duì)執(zhí)行(不能并發(fā)),線程同步機(jī)制庭砍。
2.1.1 -變量對(duì)線程安全的影響
實(shí)例變量:在堆中场晶。
靜態(tài)變量:在方法區(qū)。
局部變量:在棧中怠缸。
以上三大變量中:
局部變量永遠(yuǎn)都不會(huì)存在線程安全問題诗轻。
因?yàn)榫植孔兞坎还蚕怼#ㄒ粋€(gè)線程一個(gè)棧揭北。)
局部變量在棧中扳炬。所以局部變量永遠(yuǎn)都不會(huì)共享。
實(shí)例變量在堆中搔体,堆只有1個(gè)恨樟。
靜態(tài)變量在方法區(qū)中,方法區(qū)只有1個(gè)嫉柴。
堆和方法區(qū)都是多線程共享的厌杜,所以可能存在線程安全問題。
局部變量+常量:不會(huì)有線程安全問題计螺。
成員變量:可能會(huì)有線程安全問題夯尽。
2.1.2 -模擬線程安全問題
public class Test {
public static void main(String[] args) {
// 創(chuàng)建賬戶對(duì)象(只創(chuàng)建1個(gè))
Account act = new Account("act-001", 10000);
// 創(chuàng)建兩個(gè)線程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 設(shè)置name
t1.setName("t1");
t2.setName("t2");
// 啟動(dòng)線程取款
t1.start();
t2.start();
//t1對(duì)act-001取款5000.0成功,余額5000.0
//t2對(duì)act-001取款5000.0成功登馒,余額5000.0
}
}
----------------------------------------------------
public class AccountThread extends Thread {
// 兩個(gè)線程必須共享同一個(gè)賬戶對(duì)象匙握。
private Account act;
// 通過構(gòu)造方法傳遞過來賬戶對(duì)象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的執(zhí)行表示取款操作。
// 假設(shè)取款5000
double money = 5000;
// 取款
// 多線程并發(fā)執(zhí)行這個(gè)方法陈轿。
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "對(duì)"+act.getActno()+"取款"+money+"成功圈纺,余額" + act.getBalance());
}
}
------------------------------------------------
/**
* @author Mr.樂
* @Description
*/
public class Account {
// 賬號(hào)
private String actno;
// 余額
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
public void withdraw(double money){
// t1和t2并發(fā)這個(gè)方法秦忿。。蛾娶。灯谣。(t1和t2是兩個(gè)棧。兩個(gè)棧操作堆中同一個(gè)對(duì)象蛔琅。)
// 取款之前的余額
double before = this.getBalance(); // 10000
// 取款之后的余額
double after = before - money;
// 在這里模擬一下網(wǎng)絡(luò)延遲胎许,100%會(huì)出現(xiàn)問題
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余額
// 思考:t1執(zhí)行到這里了,但還沒有來得及執(zhí)行這行代碼罗售,t2線程進(jìn)來withdraw方法了辜窑。此時(shí)一定出問題。
this.setBalance(after);
}
}
2.2 - 線程同步的利弊
好處:解決了線程同步的數(shù)據(jù)安全問題
弊端:當(dāng)線程很多的時(shí)候寨躁,每個(gè)線程都會(huì)去判斷同步上面的這個(gè)鎖穆碎,很耗費(fèi)資源,降低效率
2.3 -編程模型
異步編程模型:
線程t1和線程t2职恳,各自執(zhí)行各自的所禀,t1不管t2,t2不管t1话肖,
誰也不需要等誰北秽,這種編程模型叫做:異步編程模型。
其實(shí)就是:多線程并發(fā)(效率較高最筒。)
同步編程模型:
線程t1和線程t2,在線程t1執(zhí)行的時(shí)候蔚叨,必須等待t2線程執(zhí)行
結(jié)束床蜘,或者說在t2線程執(zhí)行的時(shí)候,必須等待t1線程執(zhí)行結(jié)束蔑水,
兩個(gè)線程之間發(fā)生了等待關(guān)系邢锯,這就是同步編程模型。
效率較低搀别。線程排隊(duì)執(zhí)行丹擎。
2.4 -線程同步
2.4.1 -線程同步方式
同步語句塊:synchronized(this){方法體} (synchronized括號(hào)后的數(shù)據(jù)必須是多線程共享的數(shù)據(jù),才能達(dá)到多線程排隊(duì))
// 以下代碼的執(zhí)行原理歇父?
// 1蒂培、假設(shè)t1和t2線程并發(fā),開始執(zhí)行以下代碼的時(shí)候榜苫,肯定有一個(gè)先一個(gè)后护戳。
// 2、假設(shè)t1先執(zhí)行了垂睬,遇到了synchronized媳荒,這個(gè)時(shí)候自動(dòng)找“后面共享對(duì)象”的對(duì)象鎖抗悍,
// 找到之后,并占有這把鎖钳枕,然后執(zhí)行同步代碼塊中的程序缴渊,在程序執(zhí)行過程中一直都是
// 占有這把鎖的。直到同步代碼塊代碼結(jié)束鱼炒,這把鎖才會(huì)釋放衔沼。
// 3、假設(shè)t1已經(jīng)占有這把鎖田柔,此時(shí)t2也遇到synchronized關(guān)鍵字俐巴,也會(huì)去占有后面
// 共享對(duì)象的這把鎖,結(jié)果這把鎖被t1占有硬爆,t2只能在同步代碼塊外面等待t1的結(jié)束欣舵,
// 直到t1把同步代碼塊執(zhí)行結(jié)束了,t1會(huì)歸還這把鎖缀磕,此時(shí)t2終于等到這把鎖缘圈,然后
// t2占有這把鎖之后,進(jìn)入同步代碼塊執(zhí)行程序袜蚕。
//
// 這樣就達(dá)到了線程排隊(duì)執(zhí)行糟把。
// 這里需要注意的是:這個(gè)共享對(duì)象一定要選好了。這個(gè)共享對(duì)象一定是你需要排隊(duì)
// 執(zhí)行的這些線程對(duì)象所共享的牲剃。
synchronized (this){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
普通同步方法:修飾符 synchronized 返回值類型 方法名(形參列表){方法體}
synchronized出現(xiàn)在實(shí)例方法上遣疯,一定鎖的是this(此方法)。不能是其他的對(duì)象了凿傅。 所以這種方式不靈活缠犀。
另外還有一個(gè)缺點(diǎn):synchronized出現(xiàn)在實(shí)例方法上, 表示整個(gè)方法體都需要同步聪舒,可能會(huì)無故擴(kuò)大同步的 范圍辨液,導(dǎo)致程序的執(zhí)行效率降低。所以這種方式不常用箱残。
public synchronized void withdraw(double money){
double before = this.getBalance(); // 10000
// 取款之后的余額
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余額
this.setBalance(after);
}
靜態(tài)同步方法:修飾符 synchronized static 返回值類型 方法名(形參列表){方法體}
靜態(tài)方法中不能使用this)表示找類鎖滔迈。類鎖永遠(yuǎn)只有1把。
2.5 -如何解決線程安全問題
是一上來就選擇線程同步嗎被辑?synchronized
不是燎悍,synchronized會(huì)讓程序的執(zhí)行效率降低,用戶體驗(yàn)不好敷待。
系統(tǒng)的用戶吞吐量降低间涵。用戶體驗(yàn)差。在不得已的情況下再選擇
線程同步機(jī)制榜揖。
第一種方案:盡量使用局部變量代替“實(shí)例變量和靜態(tài)變量”勾哩。
第二種方案:如果必須是實(shí)例變量抗蠢,那么可以考慮創(chuàng)建多個(gè)對(duì)象,這樣
實(shí)例變量的內(nèi)存就不共享了思劳。(一個(gè)線程對(duì)應(yīng)1個(gè)對(duì)象迅矛,100個(gè)線程對(duì)應(yīng)100個(gè)對(duì)象,
對(duì)象不共享潜叛,就沒有數(shù)據(jù)安全問題了秽褒。)
第三種方案:如果不能使用局部變量,對(duì)象也不能創(chuàng)建多個(gè)威兜,這個(gè)時(shí)候
就只能選擇synchronized了销斟。線程同步機(jī)制。
2.6 -Lock
應(yīng)用場景不同椒舵,不一定要在同一個(gè)方法中進(jìn)行解鎖蚂踊,如果在當(dāng)前的方法體內(nèi)部沒有滿足解鎖需求時(shí),可以將lock引用傳遞到下一個(gè)方法中笔宿,當(dāng)滿足解鎖需求時(shí)進(jìn)行解鎖操作犁钟,方法比較靈活。
private Lock lock = new ReentrantLock();//定義Lock類型的鎖
public void withdraw(double money){
// t1和t2并發(fā)這個(gè)方法泼橘。涝动。。炬灭。(t1和t2是兩個(gè)棧醋粟。兩個(gè)棧操作堆中同一個(gè)對(duì)象。)
// 取款之前的余額
lock.lock();//上鎖
double before = this.getBalance(); // 10000
// 取款之后的余額
double after = before - money;
// 在這里模擬一下網(wǎng)絡(luò)延遲重归,100%會(huì)出現(xiàn)問題
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余額
// 思考:t1執(zhí)行到這里了昔穴,但還沒有來得及執(zhí)行這行代碼,t2線程進(jìn)來withdraw方法了提前。此時(shí)一定出問題。
this.setBalance(after);
lock.unlock();//解鎖
}
2.7 -死鎖
形成原因
當(dāng)兩個(gè)線程或者多個(gè)線程互相鎖定的情況就叫死鎖避免死鎖的原則
順序上鎖泳唠,反向解鎖狈网,不要回頭
/**
* @author Mr.樂
* @Description 死鎖
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// t1和t2兩個(gè)線程共享o1,o2
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
2.8 -守護(hù)線程
java語言中線程分為兩大類:
一類是:用戶線程
一類是:守護(hù)線程(后臺(tái)線程)
其中具有代表性的就是:垃圾回收線程(守護(hù)線程)。
守護(hù)線程的特點(diǎn):
一般守護(hù)線程是一個(gè)死循環(huán)笨腥,所有的用戶線程只要結(jié)束拓哺,
守護(hù)線程自動(dòng)結(jié)束。注意:主線程main方法是一個(gè)用戶線程脖母。
守護(hù)線程用在什么地方呢士鸥?
每天00:00的時(shí)候系統(tǒng)數(shù)據(jù)自動(dòng)備份。
這個(gè)需要使用到定時(shí)器谆级,并且我們可以將定時(shí)器設(shè)置為守護(hù)線程烤礁。
一直在那里看著讼积,每到00:00的時(shí)候就備份一次。所有的用戶線程
如果結(jié)束了脚仔,守護(hù)線程自動(dòng)退出勤众,沒有必要進(jìn)行數(shù)據(jù)備份了。
public class Demo09 {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("備份數(shù)據(jù)的線程");
// 啟動(dòng)線程之前鲤脏,將線程設(shè)置為守護(hù)線程
t.setDaemon(true);
t.start();
// 主線程:主線程是用戶線程
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread {
public void run(){
int i = 0;
// 即使是死循環(huán)们颜,但由于該線程是守護(hù)者,當(dāng)用戶線程結(jié)束猎醇,守護(hù)線程自動(dòng)終止窥突。
while(true){
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3 -定時(shí)器
定時(shí)器的作用:
間隔特定的時(shí)間,執(zhí)行特定的程序硫嘶。
在java的類庫中已經(jīng)寫好了一個(gè)定時(shí)器:java.util.Timer阻问,可以直接拿來用。
不過音半,這種方式在目前的開發(fā)中也很少用则拷,因?yàn)楝F(xiàn)在有很多高級(jí)框架都是支持
定時(shí)任務(wù)的。
import java.util.Timer;
import java.util.TimerTask;
/**
* @author Mr.樂
* @Description 定時(shí)類
*/
public class DemoTimer {
public static void main(String[] args) {
Timer timer = new Timer();//創(chuàng)建Timer定時(shí)器類的對(duì)象
//匿名內(nèi)部類
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("我被執(zhí)行了曹鸠!~");
System.gc();//告訴JVM運(yùn)行完畢煌茬,可以把我回收
}
},5000);
}
}
3.1 -線程與定時(shí)器執(zhí)行軌跡不同
線程與定時(shí)器之間互不搶占CPU時(shí)間片
import java.util.Timer;
import java.util.TimerTask;
/**
* @author Mr.樂
* @Description 線程與定時(shí)器的執(zhí)行軌跡不同
*/
public class DemoTimer {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "<--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//定時(shí)器實(shí)現(xiàn)
new Timer().schedule(new TimerTask() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.gc();//將編程垃圾的定時(shí)器進(jìn)行回收
}
},5000);
}
}
4 -生產(chǎn)者和消費(fèi)者
4.1 -關(guān)于Object類中的wait和notify方法。
第一:wait和notify方法不是線程對(duì)象的方法彻桃,是java中任何一個(gè)java對(duì)象都有的方法坛善,因?yàn)檫@兩個(gè)方式是Object類中自帶的。
wait方法和notify方法不是通過線程對(duì)象調(diào)用邻眷,
不是這樣的:t.wait()眠屎,也不是這樣的:t.notify()..不對(duì)。
第二:wait()方法作用:
Object o = new Object();
o.wait();
表示:
讓正在o對(duì)象上活動(dòng)的線程進(jìn)入等待狀態(tài)肆饶,無期限等待改衩,直到被喚醒為止。
o.wait();方法的調(diào)用驯镊,會(huì)讓“當(dāng)前線程(正在o對(duì)象上活動(dòng)的線程)”進(jìn)入等待狀態(tài)葫督。
第三:notify()方法作用:
Object o = new Object();
o.notify();
表示:喚醒正在o對(duì)象上等待的線程。還有一個(gè)notifyAll()方法:這個(gè)方法是喚醒o對(duì)象上處于等待的所有線程板惑。
注意:wait方法和notify方法需要建立在synchronized線程同步的基礎(chǔ)之上橄镜。
重點(diǎn):o.wait()方法會(huì)讓正在o對(duì)象上活動(dòng)的當(dāng)前線程進(jìn)入等待狀態(tài),并且釋放之前占有的o對(duì)象的鎖冯乘; o.notify()方法只會(huì)通知洽胶,不會(huì)釋放之前占有的o對(duì)象的鎖。
4.2 -生產(chǎn)者和消費(fèi)者模式
生產(chǎn)者與消費(fèi)者模式是并發(fā)裆馒、多線程編程中經(jīng)典的設(shè)計(jì)模式姊氓,通過wait和notifyAll方法實(shí)現(xiàn)丐怯。
例如:生產(chǎn)滿了,就不能繼續(xù)生產(chǎn)了他膳,必須讓消費(fèi)線程進(jìn)行消費(fèi)响逢。
消費(fèi)完了,就不能繼續(xù)消費(fèi)了棕孙,必須讓生產(chǎn)線程進(jìn)行生產(chǎn)舔亭。
而消費(fèi)和生產(chǎn)者共享的倉庫,就為多線程共享的了蟀俊,所以需要考慮倉庫的線程安全問題钦铺。
wait方法和notify方法建立在線程同步的基礎(chǔ)之上。因?yàn)槎嗑€程要同時(shí)操作一個(gè)倉庫肢预。有線程安全問題矛洞。
wait方法作用:o.wait()讓正在o對(duì)象上活動(dòng)的線程t進(jìn)入等待狀態(tài),并且釋放掉t線程之前占有的o對(duì)象的鎖烫映。
notify方法作用:o.notify()讓正在o對(duì)象上等待的線程喚醒沼本,只是通知,不會(huì)釋放o對(duì)象上之前占有的鎖锭沟。
例1:
/**
* @author Mr.樂
* @Description 生產(chǎn)者和消費(fèi)者模式
*/
public class wait_notify {
public static void main(String[] args) {
Box box = new Box();//實(shí)例化奶箱類
Producer producer = new Producer(box);//生產(chǎn)者對(duì)象
Customer customer = new Customer(box);//消費(fèi)者對(duì)象
Thread tp = new Thread(producer);//創(chuàng)建生產(chǎn)者線程
Thread tc = new Thread(customer);//創(chuàng)建消費(fèi)者線程
//啟動(dòng)線程
tp.start();
tc.start();
}
}
//奶箱類
class Box{
private int milk; //放入奶箱中的第幾瓶牛奶
private boolean state = false; //默認(rèn)奶箱為空
/**
* 生產(chǎn)者生產(chǎn)(放)牛奶
* @param milk 第幾瓶
*/
public synchronized void put(int milk){
if(state){ //true表示奶箱中有牛奶
try {
wait(); //等待抽兆,需要有人喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//沒有牛奶,需要生產(chǎn)牛奶
this.milk = milk;
System.out.println("王五將第" + this.milk + "瓶你牛奶放進(jìn)了奶箱中");
this.state = true;//將奶箱狀態(tài)調(diào)整成有牛奶
notifyAll();//喚醒全部正在等待的線程
}
/**
* 消費(fèi)者取牛奶
*/
public synchronized void get(){
if(!state){ //true表示奶箱中有牛奶
try {
wait(); //等待族淮,需要有人喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有牛奶辫红,需要取牛奶
System.out.println("張三將第" + this.milk + "瓶牛奶拿走補(bǔ)了身體!");
this.state = false;//將奶箱狀態(tài)改變成空
notifyAll();//喚醒全部正在等待的線程
}
}
//生產(chǎn)者類
class Producer implements Runnable{
private Box b;
public Producer(Box b){
this.b = b;
}
@Override
public void run() {
for (int i = 1; i < 8; i++) {
b.put(i);//放牛奶祝辣,放幾瓶
}
}
}
//消費(fèi)者類
class Customer implements Runnable{
private Box b;
public Customer(Box b){
this.b = b;
}
@Override
public void run() {
while (true){
b.get();//消費(fèi)者取牛奶
}
}
}
例2:
import java.util.ArrayList;
import java.util.List;
/**
* @author Mr.樂
* @Description 生產(chǎn)者和消費(fèi)者模式02
*/
public class ThreadTest16 {
public static void main(String[] args) {
// 創(chuàng)建1個(gè)倉庫對(duì)象贴妻,共享的。
List list = new ArrayList();
// 創(chuàng)建兩個(gè)線程對(duì)象
// 生產(chǎn)者線程
Thread t1 = new Thread(new Producer(list));
// 消費(fèi)者線程
Thread t2 = new Thread(new Consumer(list));
t1.setName("生產(chǎn)者線程");
t2.setName("消費(fèi)者線程");
t1.start();
t2.start();
}
}
// 生產(chǎn)線程
class Producer implements Runnable {
// 倉庫
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直生產(chǎn)(使用死循環(huán)來模擬一直生產(chǎn))
while(true){
// 給倉庫對(duì)象list加鎖。
synchronized (list){
if(list.size() > 0){ // 大于0,說明倉庫中已經(jīng)有1個(gè)元素了碟摆。
try {
// 當(dāng)前線程進(jìn)入等待狀態(tài),并且釋放Producer之前占有的list集合的鎖绢片。
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能夠執(zhí)行到這里說明倉庫是空的,可以生產(chǎn)
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 喚醒消費(fèi)者進(jìn)行消費(fèi)
list.notifyAll();
}
}
}
}
// 消費(fèi)線程
class Consumer implements Runnable {
// 倉庫
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直消費(fèi)
while(true){
synchronized (list) {
if(list.size() == 0){
try {
// 倉庫已經(jīng)空了岛琼。
// 消費(fèi)者線程等待,釋放掉list集合的鎖
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能夠執(zhí)行到此處說明倉庫中有數(shù)據(jù)巢株,進(jìn)行消費(fèi)槐瑞。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 喚醒生產(chǎn)者生產(chǎn)。
list.notifyAll();
}
}
}
}
5 -線程池
5.1 - 概念
線程池就是首先創(chuàng)建一些線程阁苞,他們的集合稱之為線程池困檩。線程池在系統(tǒng)啟動(dòng)時(shí)會(huì)創(chuàng)建大量空閑線程祠挫,程序?qū)⒁粋€(gè)任務(wù)傳遞給線程池,線程池就會(huì)啟動(dòng)一條線程來執(zhí)行這個(gè)任務(wù)悼沿,執(zhí)行結(jié)束后線程不會(huì)銷毀(死亡)等舔,而是再次返回到線程池中成為空閑狀態(tài),等待執(zhí)行下一個(gè)任務(wù)糟趾。
5.2 - 線程池的工作機(jī)制
在線程池的編程模式下慌植,任務(wù)是分配給整個(gè)線程池的,而不是直接提交給某個(gè)線程义郑,線程池拿到任務(wù)后蝶柿,就會(huì)在內(nèi)部尋找是否有空閑的線程,如果有非驮,則將任務(wù)交個(gè)某個(gè)空閑線程交汤。
5.3 - 使用線程池的原因
多線程運(yùn)行時(shí),系統(tǒng)不斷創(chuàng)建和銷毀新的線程劫笙,成本非常高芙扎,會(huì)過度的消耗系統(tǒng)資源,從而可能導(dǎo)致系統(tǒng)資源崩潰填大,使用線程池就是最好的選擇戒洼。
5.4 - 可重用線程
方法名 | 說明 |
---|---|
Executors.newCacheThreadPoll(); | 創(chuàng)建一個(gè)可緩存的線程池 |
execute(Runnable run) | 啟動(dòng)線程池中的線程 |
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Mr.樂
* @Description 可重用線程池
*/
public class ExecutorsTest {
public static void main(String[] args) {
//創(chuàng)建線程池
ExecutorService threadPoll = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
//如果不睡眠,那么第一個(gè)執(zhí)行完的線程無法及時(shí)成為空閑線程栋盹,那么線程池就會(huì)讓一個(gè)新的線程執(zhí)行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每次循環(huán)都會(huì)開啟一個(gè)線程
threadPoll.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在被執(zhí)行施逾!~");
}
});
}
threadPoll.shutdown();//關(guān)閉線程池
//線程池是無限大,當(dāng)執(zhí)行當(dāng)前任務(wù)時(shí)例获,上一個(gè)任務(wù)已經(jīng)完成汉额,會(huì)重復(fù)執(zhí)行上一個(gè)任務(wù)的線程,而不是每次使用新的線程
}
}
6 -多線程并發(fā)的線程安全問題
了解了線程池榨汤,接下來從底層講一下多線程并發(fā)的安全問題蠕搜。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Mr.樂
* @Description 并發(fā)安全
*/
public class MyTest {
//定義靜態(tài)變量
static int a=0;
static int count=2000;
public static void main(String[] args) {
//創(chuàng)建線程池
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<count;i++){
service.execute(new Runnable() {
@Override
public void run() {
a++;
}
});
}
關(guān)閉線程池
service.shutdown();
System.out.println(a);
//1987
}
}
以上程序運(yùn)行并沒有達(dá)到預(yù)期的2000,此處多線程并發(fā)收壕,a共享妓灌,所以沒達(dá)到2000
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Mr.樂
* @Description 并發(fā)安全
*/
public class MyTest {
static int a=0;
static int count=2000;
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
//閉鎖 在一些條件下可放開 參數(shù):加多少把鎖
CountDownLatch countDownLatch=new CountDownLatch(count);
for(int i=0;i<count;i++){
service.execute(new Runnable() {
@Override
public void run() {
a++;
//解一把鎖
countDownLatch.countDown();
}
});
}
service.shutdown();
//會(huì)進(jìn)入阻塞狀態(tài) 什么時(shí)候把鎖全解了 阻塞狀態(tài)才會(huì)解除
countDownLatch.await();
System.out.println(a);
//1987
}
}
此處所用的加鎖方法也沒有實(shí)現(xiàn)預(yù)期效果。
6.1 -CPU多級(jí)緩存
打開任務(wù)管理器蜜宪,在性能中可查看CPU的多級(jí)緩存虫埂。
程序進(jìn)程中的數(shù)據(jù),都在內(nèi)存中存著圃验。 而CPU緩存掉伏,是為了解決內(nèi)存沒有CPU快的問題。當(dāng)一個(gè)數(shù)據(jù)需要CPU修改,而內(nèi)存無法及時(shí)給CPU返回?cái)?shù)據(jù)斧散,就會(huì)拖慢CPU的運(yùn)行速度供常。所以有了CPU緩存。
當(dāng)CPU需要在內(nèi)存中讀數(shù)據(jù)時(shí)鸡捐,在時(shí)間局部性上(不久的將來)還得讀此數(shù)據(jù)栈暇。,將此數(shù)據(jù)放在CPU緩存中箍镜。
當(dāng)用到內(nèi)存中數(shù)據(jù)(例如 a)時(shí)源祈,而數(shù)據(jù)旁邊的數(shù)據(jù)(例:static int a=0; int b=0; 用a時(shí)b為旁邊的數(shù)據(jù))在空間局部性上,會(huì)用到相鄰的數(shù)據(jù)(例如 b)鹿寨,CPU也會(huì)讀到b新博,將b數(shù)據(jù)放在CPU緩存中。
當(dāng)CPU讀取數(shù)據(jù)時(shí)脚草,會(huì)讓CPU緩存同步內(nèi)存中的數(shù)據(jù)赫悄。然后CPU緩存中的數(shù)據(jù)再交給CPU去修改。當(dāng)CPU修改完后馏慨,會(huì)把修改的數(shù)據(jù)傳給CPU緩存(此時(shí)CPU不需要等待)埂淮,再由CPU緩存?zhèn)鹘o內(nèi)存 。
當(dāng)CPU 01將數(shù)據(jù)修改完后写隶,CPU緩存01還沒有將數(shù)據(jù)傳給內(nèi)存倔撞,CPU緩存02讀到了a,此時(shí)a的值為0慕趴。
以下為線程安全的兩種方式痪蝇。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Mr.樂
* @Description 并發(fā)安全 synchronized
*/
public class MyTest {
static int a=0;
static int count=2000;
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
//閉鎖 在一些條件下可放開 參數(shù):加多少把鎖
CountDownLatch countDownLatch=new CountDownLatch(count);
for(int i=0;i<count;i++){
service.execute(new Runnable() {
@Override
public void run() {
synchronized (MyTest.class) {
a++;
//解一把鎖
countDownLatch.countDown();
}
}
});
}
service.shutdown();
//會(huì)進(jìn)入阻塞狀態(tài) 什么時(shí)候把鎖全解了 阻塞狀態(tài)才會(huì)解除
countDownLatch.await();
System.out.println(a);
//2000
}
}
-------------------------------------------------------------------
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author Mr.樂
* @Description 并發(fā)安全 synchronized
*/
public class MyTest {
static int a=0;
static int count=2000;
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
//閉鎖 在一些條件下可放開 參數(shù):加多少把鎖
CountDownLatch countDownLatch=new CountDownLatch(count);
//信號(hào)量
Semaphore semaphore=new Semaphore(1);
for(int i=0;i<count;i++){
service.execute(new Runnable() {
@Override
public void run() {
try { //拿走一個(gè)信號(hào)
semaphore.acquire();
a++;
//解一把鎖
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//釋放信號(hào)
semaphore.release();
}
}
});
}
service.shutdown();
//會(huì)進(jìn)入阻塞狀態(tài) 什么時(shí)候把鎖全解了 阻塞狀態(tài)才會(huì)解除
countDownLatch.await();
System.out.println(a);
//2000
}
}