什么是進程?什么是線程
進程是一個應用程序(1個進程是一個軟件)。
線程是一個進程中的執(zhí)行場景/執(zhí)行單元旗唁。
一個進程可以啟動多個線程。
對于java程序來說痹束,當在DOS命令窗口中輸入:
java HelloWorld 回車之后检疫。
會先啟動JVM,而JVM就是一個進程祷嘶。
JVM再啟動一個主線程調用main方法屎媳。
同時再啟動一個垃圾回收線程負責看護,回收垃圾论巍。
最起碼烛谊,現在的java程序中至少有兩個線程并發(fā),一個是垃圾回收線程嘉汰,一個是執(zhí)行main方法的主線程丹禀。
進程和線程是什么關系?舉個例子
阿里巴巴:進程
馬云:阿里巴巴的一個線程
童文紅:阿里巴巴的一個線程
京東:進程
強東:京東的一個線程
妹妹:京東的一個線程
進程可以看做是現實生活當中的公司鞋怀。
線程可以看做是公司當中的某個員工双泪。
注意:
進程A和進程B的內存獨立不共享。
線程A和線程B呢密似?
在java語言中:線程A和線程B焙矛,堆內存和方法區(qū)內存共享。但是棧內存獨立残腌,一個線程一個棧薄扁。
假設啟動10個線程,會有10個椃侠郏空間,每個棧和每個棧之間脱盲,互不干擾邑滨,各自執(zhí)行各自的,這就是多線程并發(fā)钱反。
火車站可以看作是一個進程掖看。
火車站中的每一個售票窗口可以看做是一個線程匣距。
我在窗口1購票,你可以在窗口2購票哎壳,你不需要等我毅待,我也不需要等你。
所以多線程并發(fā)可以提高效率归榕。
java中之所以有多線程機制尸红,目的就是為了提高程序的處理效率。
使用了多線程機制之后刹泄,main方法結束外里,是不是有可能程序也不會結束。main方法結束只是主線程結束了特石,主椫鸦龋空了,其它的棧(線程)可能還在壓棧彈棧姆蘸。
對于單核的CPU來說墩莫,真的可以做到真正的多線程并發(fā)嗎?
對于多核的CPU電腦來說逞敷,真正的多線程并發(fā)是沒問題的狂秦。
4核CPU表示同一個時間點上,可以真正的有4個進程并發(fā)執(zhí)行兰粉。
什么是真正的多線程并發(fā)故痊?
t1線程執(zhí)行t1的。
t2線程執(zhí)行t2的玖姑。
t1不會影響t2.t2也不會影響t1愕秫。這叫做真正的多線程并發(fā)。
單核的CPU表示只有一個大腦:
不能夠做到真正的多線程并發(fā)焰络,但是可以做到給人一種“多線程并發(fā)”的感覺戴甩。
對于單核的CPU來說,在某一個時間點上實際上只能處理一件事情闪彼,但是由于CPU的處理速度極快甜孤,多個線程之間頻繁切換執(zhí)行,給人類的感覺就是:多個事情同時在做N吠蟆=纱ā!描馅!
實現線程的方式
java支持多線程機制把夸。并且Java已經將多線程實現了,我們只需要繼承就行了铭污。
實現線程有兩種方式恋日,哪兩種方式呢膀篮?
第一種方式:編寫一個類,直接繼承java.lang.Thread岂膳,重寫run方法誓竿。
//定義線程類
public class MyThread extends Thread{
public void run(){
}
}
//創(chuàng)建線程對象
MyThread t = new MyThread();
//啟動線程
t.start();
注意:
亙古不變的道理:方法體當中的代碼永遠都是自上而下的順序依次逐行執(zhí)行的。
public class Text {
public static void main(String[] args){
//這里是main方法谈截,這里的代碼屬于主線程筷屡,在主棧中運行。
//新建一個分支線程對象
MyThread t = new MyThread();
//啟動線程
//t.run();//不會啟動線程傻盟,不會分配新的分支棧速蕊。(這種方式就是單線程。)
//start()方法的作用是:啟動一個分支線程娘赴,在JVM中開辟一個新的椆嬲埽空間,這段代碼任務完成之后诽表,瞬間就結束了唉锌。
//這段代碼的任務只是為了開啟一個新的棧空間竿奏,只要新的棸兰颍空間開出來,start()方法就結束了泛啸。線程就啟動成功了绿语。
//啟動成功的線程會自動調用run方法,并且run方法在分支棧的棧底部(壓棧)候址。
//run方法在分支棧的棧底部吕粹,main方法在主棧的棧底部。run和main是平級的岗仑。
t.start();
//這里的代碼還是運行在主線程中匹耕。
for (int i = 0; i < 1000; i++) {
System.out.println("主線程--->" + i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//編寫程序,這段程序運行在分支線程中(分支棧)荠雕。
for (int i = 0; i < 1000; i++) {
System.out.println("分支線程--->" + i);
}
}
}
第二種方式:編寫一個類稳其,實現java.lang.Runnable接口,實現run方法炸卑。
//定義一個可運行的類
public class MyRunnable implements Runnable{
public void run(){
}
}
//創(chuàng)建線程對象
Thread t = new Thread(new MyRunnable());
//啟動線程
t.start();
public class Text {
public static void main(String[] args){
//創(chuàng)建一個可運行的對象
//MyRunnable r = new MyRunnable();
//將可運行的對象封裝成一個線程對象
//Thread t = new Thread(r);
//合并代碼
Thread t = new Thread(new MyRunnable());
//啟動線程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主線程--->" + i);
}
}
}
//這并不是一個線程類既鞠,是一個可運行的類。它還不是一個線程盖文。
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支線程--->" + i);
}
}
}
注意:第二種方式實現接口比較常用损趋,因為一個類實現了接口,它還可以去繼承其它的類,更靈活浑槽。
可以采用匿名內部類來實現run方法
public class Text {
public static void main(String[] args){
//創(chuàng)建一個線程對象,采用匿名內部類的方式返帕。
//這是通過一個沒有名字的類桐玻,new出來的對象。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("t線程--->" + i);
}
}
});
for (int i = 0; i < 100; i++) {
System.out.println("main線程--->" + i);
}
}
}
線程的生命周期
新建狀態(tài)
就緒狀態(tài)
運行狀態(tài)
阻塞狀態(tài)
死亡狀態(tài)
獲取線程名字
獲取線程對象的名字:
String name = 線程對象.getName();
修改線程對象的名字:
線程對象.setName("線程名字");
當線程沒有設置名字的時候荆萤,默認的名字有什么規(guī)律镊靴?(了解一下)
Thread-0
Thread-1
Thread-2
......
獲取當前線程對象
static Thread currentThread()
Thread t = Thread.currentThread();
System.out.println(t.getName());
返回值t就是當前線程
t就是當前線程對象。當前線程是誰呢链韭?
出現在主方法中偏竟,那就是主線程是當前線程。
當t1線程執(zhí)行run方法敞峭,那么這個當前線程就是t1踊谋。
當t2線程執(zhí)行run方法,那么這個當前線程就是t2旋讹。
線程的sleep()方法
static void sleep(Long millis)
靜態(tài)方法
參數是毫秒
作用:讓當前線程進入休眠殖蚕,進入“阻塞狀態(tài)”,放棄占有CPU時間片沉迹,讓給其它線程使用睦疫。
Thread.sleep()方法,可以做到這種效果:
間隔特定時間鞭呕,去執(zhí)行一段特定的代碼蛤育,每隔多久執(zhí)行一次。
public class Text {
public static void main(String[] args) {
//讓當前線程進入休眠葫松,睡眠5秒
//當前線程是主線程M吒狻!
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后執(zhí)行這里的代碼
System.out.println("hello world!");
}
}
public class Text {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + i);
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
面試題:
public class Text {
public static void main(String[] args) {
//創(chuàng)建線程對象
Thread t = new MyThread();
t.setName("t");
t.start();
//調用sleep方法
try {
//問題:這行代碼會讓線程t進入休眠狀態(tài)么进宝?不會
//這行代碼的作用是:讓當前線程進入休眠刻坊,也就是main線程進入休眠。
//這行代碼出現在main方法中党晋,main方法睡眠谭胚。
//雖然是用t調用的,但是跟t沒關系未玻,sleep方法是靜態(tài)方法
t.sleep(1000 * 5);//在執(zhí)行的時候還是會轉換成:Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后灾而,這里才會執(zhí)行
System.out.println("hello world!");
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
怎么叫醒一個正在睡眠的線程扳剿?
注意:這個不是終止線程的執(zhí)行旁趟,是終止線程的睡眠。
public class Text {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
//希望五秒之后庇绽,t線程醒來(就是這5秒鐘锡搜,假設主線程的活兒干完了)
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
//打印異常信息
e.printStackTrace();
}
//終止t線程的睡眠(這種終斷睡眠的方式依靠了java的異常處理機制橙困。)
t.interrupt();//干擾,一盆冷水過去耕餐!
}
}
class MyRunnable implements Runnable{
//重點:run()當中的異常不能throws凡傅,只能try catch
//因為run()方法在父類中沒有拋出任何異常,子類不能比父類拋出更多的異常肠缔。
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---> begin");
try {
//睡眠一年
Thread.sleep(1000L * 60L * 60L * 24L * 365L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1年之后才會執(zhí)行這里
System.out.println(Thread.currentThread().getName() + "---> end");
}
}
終止線程
在java中如何強行終止一個線程的執(zhí)行
這種方式存在很大的缺點:容易丟失數據夏跷。因為這種方式是直接將線程殺死了,線程沒有保存的數據將會丟失明未。不建議使用槽华。
public class Text {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
//模擬5秒
try {
Thread.sleep(100 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后強行終止t線程
t.stop();//已過時(不建議使用)
}
}
class MyRunnable implements Runnable{
@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();
}
}
}
}
怎么合理的終止一個線程的執(zhí)行,這種方式是很常用的趟妥。
public class Text {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.setName("t");
t.start();
//模擬5秒
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//終止線程
//你想要什么時候終止t的執(zhí)行猫态,把標記修改為false,就結束了煮纵。
r.run = false;
}
}
class MyRunnable implements Runnable{
//打一個布爾標記
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就結束了懂鸵,在結束之前還有什么每保存的。
//在這里就可以保存了
//save.....
//終止當前線程
return;
}
}
}
}
線程調度(了解)
常見的線程調度模型有哪些行疏?
搶占式調度模型:
哪個線程的優(yōu)先級比較高匆光,搶到的CPU時間片的概率就高一些/多一些。
java種采用的就是搶占式調度模型酿联。
均分式調度模型:
平均分配CPU時間片终息。每個線程占有的CPU時間片時間長度一樣。
平均分配贞让,一切平等周崭。
有一些編程語言,線程調度模型采用的是這種方式喳张。
java中提供了哪些方法是和線程調度有關系的呢续镇?
實例方法:
void setPriority(int newPriority) 設置線程的優(yōu)先級
int getPriority() 獲取線程的優(yōu)先級
最低優(yōu)先級是1
默認優(yōu)先級是5
最高優(yōu)先級是10
優(yōu)先級比較高的獲取CPU時間片可能會多一些。(但也不完全是销部,大概率是多的摸航。)
靜態(tài)方法:
static void yield() 讓位方法
暫停當前正在執(zhí)行的線程對象,并執(zhí)行其他線程
yield()方法不是阻塞方法舅桩。讓當前線程讓位酱虎,讓給其它線程使用。
yield()方法的執(zhí)行會讓當前線程從“運行狀態(tài)”回到“就緒狀態(tài)”擂涛。
注意:在回到就緒之后读串,有可能還會再次搶到CPU時間片。
實例方法:
void join() 合并線程
class MyThread1 extends Thread {
public void doSome(){
MyThread2 t = new MyThread2();
t.join;//當前線程進入阻塞,t線程執(zhí)行恢暖,直到t線程結束排监。當前線程才可以繼續(xù)執(zhí)行。
}
}
class MyThread2 extends Thread{
}
關于線程優(yōu)先級:
最高優(yōu)先級:Thread.MAX_PRIORITY
最低優(yōu)先級:Thread.MIN_PRIORITY
默認優(yōu)先級:Thread.NORM_PRIORITY
優(yōu)先級較高的杰捂,只是搶到的CPU時間片相對多一些社露。
大概率方向偏向于優(yōu)先級較高的
處于運行狀態(tài)的時間多一些。
設置線程優(yōu)先級:
Thread.currentThread().setPriority(1);//設置優(yōu)先級為1
t.setPriority(10);//設置t線程的優(yōu)先級為10
關于線程讓位:
當前線程暫颓砟铮回到就緒狀態(tài),讓給其他線程附鸽。
靜態(tài)方法:Thread.yield();
關于線程合并:
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
t.join();//t合并到當前線程中脱拼,當前線程受阻塞,t線程執(zhí)行坷备,直到結束熄浓。
線程安全(重點)
關于多線程并發(fā)環(huán)境下,數據的安全問題省撑。
為什么這個是重點赌蔑?
以后在開發(fā)中,我們的項目都是運行在服務器當中竟秫,而且服務器已經將線程的定義娃惯,線程對象的創(chuàng)建,線程的啟動等肥败,都已經實現完了趾浅。這些代碼我們都不需要編寫。
最重要的是:你要知道馒稍,你編寫的程序需要放到一個多線程的環(huán)境下運行皿哨,你更需要關注的是這些數據在多線程并發(fā)的環(huán)境下是否是安全的。
什么時候數據在多線程并發(fā)的環(huán)境下會存在安全問題呢纽谒?
三個條件:
條件一:多線程并發(fā)证膨。
條件2:有共享數據。
條件3:共享數據有修改的行為鼓黔。
滿足以上三個條件以后央勒,就會存在線程安全問題。
怎么解決線程安全問題呢请祖?
當多線程并發(fā)的環(huán)境下订歪,有共享數據,并且這個數據還會被修改肆捕,此時就存在線程安全問題刷晋,怎么解決這個問題?
線程排隊執(zhí)行。(不能并發(fā))
用排隊執(zhí)行解決線程安全問題眼虱。
這種機制被稱為:線程同步機制喻奥。
專業(yè)術語叫做:線程同不,實際上就是線程不能并發(fā)了捏悬,線程必須排隊執(zhí)行撞蚕。
線程同步就是線程排隊了,線程排隊了就回犧牲一部分效率过牙,沒辦法甥厦,數據安全第一位,只有數據安全了寇钉,我們才可以談效率刀疙。數據不安全,沒有效率的事兒扫倡。
線程同不這塊谦秧,設計到這兩個專業(yè)術語:
異步編程模型:
線程t1和線程t2,各自執(zhí)行各自的撵溃,t1不管t2疚鲤,t2不管t1,誰也不用等誰缘挑,這種編程模型叫做:異步編程模型集歇。
其實就是:多線程并發(fā)(效率較高。)
異步就是并發(fā)卖哎。
同步編程模型:
線程t1和線程t2鬼悠,在線程t1執(zhí)行的時候,必須等待t2線程執(zhí)行結束亏娜,或者說在t2線程執(zhí)行的時候焕窝,必須等待t1線程執(zhí)行結束,兩個線程之間發(fā)生了等待關系维贺,這就是同步編程模型它掂。
效率較低。線程排隊執(zhí)行溯泣。
同步就是排隊虐秋。
不使用線程同步機制,多線程對同一個賬戶進行取款垃沦,出現線程安全問題客给。
public class Text {
public static void main(String[] args) {
// 創(chuàng)建賬戶對象(只創(chuàng)建1個)
Account act = new Account("act-001", 10000);
// 創(chuàng)建兩個線程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 設置name
t1.setName("t1");
t2.setName("t2");
// 啟動線程取款
t1.start();
t2.start();
}
}
class AccountThread extends Thread {
// 兩個線程必須共享同一個賬戶對象。
private Account act;
// 通過構造方法傳遞過來賬戶對象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的執(zhí)行表示取款操作肢簿。
// 假設取款5000
double money = 5000;
// 取款
// 多線程并發(fā)執(zhí)行這個方法靶剑。
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "對"+act.getActno()+"取款"+money+"成功蜻拨,余額" + act.getBalance());
}
}
/*
銀行賬戶
不使用線程同步機制,多線程對同一個賬戶進行取款桩引,出現線程安全問題缎讼。
*/
class Account {
// 賬號
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ā)這個方法。坑匠。血崭。。(t1和t2是兩個棧厘灼。兩個棧操作堆中同一個對象夹纫。)
// 取款之前的余額
double before = this.getBalance(); // 10000
// 取款之后的余額
double after = before - money;
// 在這里模擬一下網絡延遲,100%會出現問題
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余額
// 思考:t1執(zhí)行到這里了设凹,但還沒有來得及執(zhí)行這行代碼捷凄,t2線程進來withdraw方法了。此時一定出問題围来。
this.setBalance(after);
}
}
使用線程同步機制,解決線程安全問題贤壁。
線程同步機制的語法是:
synchronized(){
//線程同步代碼塊
}
synchronized后面小括號中傳的這個“數據”是相當關鍵的寸潦。
這個數據必須是多線程共享的數據瑟慈。才能達到多線程排隊。
()中寫什么
那要看你想讓哪些線程同步胀蛮。
假設t1、t2糯钙、t3粪狼、t4、t5任岸,有5個線程再榄,你只希望t1、t2享潜、t3排隊困鸥,t4、t5不需要排隊剑按。怎么辦疾就?
你一定要在()中寫一個t1 t2 t3 共享的對象。而這個對象對于t4 t5來說不是共享的艺蝴。
public class Text {
public static void main(String[] args) {
// 創(chuàng)建賬戶對象(只創(chuàng)建1個)
Account act = new Account("act-001", 10000);
// 創(chuàng)建兩個線程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 設置name
t1.setName("t1");
t2.setName("t2");
// 啟動線程取款
t1.start();
t2.start();
}
}
class AccountThread extends Thread {
// 兩個線程必須共享同一個賬戶對象猬腰。
private Account act;
// 通過構造方法傳遞過來賬戶對象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的執(zhí)行表示取款操作。
// 假設取款5000
double money = 5000;
// 取款
// 多線程并發(fā)執(zhí)行這個方法猜敢。
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "對"+act.getActno()+"取款"+money+"成功姑荷,余額" + act.getBalance());
}
}
/*
銀行賬戶
使用線程同步機制盒延,解決安全問題。
*/
class Account {
// 賬號
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){
//以下這幾行代碼必須是線程排隊的厢拭,不能并發(fā)兰英。
//一個線程把這里的代碼全部執(zhí)行結束之后,另一個線程才能進來供鸠。
/*
這里的共享對象是:賬戶對象畦贸。
賬戶對象是共享的,那么this就是賬戶對象吧@阄妗薄坏!
小括號里不一定是this,這里只要是多線程共享的那個對象就行寨闹。
*/
synchronized (this){
// 取款之前的余額
double before = this.getBalance(); // 10000
// 取款之后的余額
double after = before - money;
// 在這里模擬一下網絡延遲胶坠,100%會出現問題
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余額
// 思考:t1執(zhí)行到這里了,但還沒有來得及執(zhí)行這行代碼繁堡,t2線程進來withdraw方法了沈善。此時一定出問題。
this.setBalance(after);
}
}
}
對synchronized的理解
在java語言中任何一個對象都有“一把鎖”椭蹄,其實這把鎖就是標記闻牡。(只是把它叫做鎖。)
100個對象绳矩,100把鎖罩润。1個對象1把鎖。
假設t1和t2線程并發(fā)翼馆,開始執(zhí)行同步代碼塊代碼的時候割以,肯定有一個先有一個后。
假設t1先執(zhí)行了应媚,遇到了synchronized严沥,這個時候自動找“后面共享對象”的對象鎖,找到之后中姜,并占有這把鎖祝峻,然后執(zhí)行同步代碼塊中的程序,在程序執(zhí)行過程中一致都是占有這把鎖的扎筒。知道同步代碼塊結束莱找,這把鎖才會釋放。
假設t1已經占有這把鎖嗜桌,此時t2也遇到了synchronized關鍵字奥溺,也會去占有后面共享對象的這把鎖,結果這把鎖被t1占有骨宠,t2只能在同步代碼塊外面等待t1的結束浮定,直到t1把同步代碼塊執(zhí)行結束了相满,t1會歸還這把鎖,此時t2終于等到這把鎖桦卒,然后t2占有這把鎖之后立美,進入同步代碼塊執(zhí)行程序。
這樣就達到了線程排隊執(zhí)行方灾。
這里需要注意的是:這個共享對象一定要選好了建蹄。這個共享對象一定是你需要排隊執(zhí)行的這些線程對象所共享的。
哪些變量有線程安全問題
Java中有三大變量
實例變量:在堆中裕偿。
靜態(tài)變量:在方法區(qū)洞慎。
局部變量:在棧中。
以上三大變量中:
局部變量永遠都不會存在線程安全問題嘿棘。因為局部變量不共享劲腿。(一個線程一個棧。)
局部變量在棧中鸟妙,所以局部變量永遠都不會共享焦人。
實例變量在堆中,堆只有一個重父。
靜態(tài)變量在方法區(qū)中垃瞧,方法區(qū)只有1個。
堆和方法區(qū)都是多線程共享的坪郭,所以可能存在線程安全問題。
擴大同步范圍:
在調用方法的時候脉幢,我用個synchronized歪沃,也行,只不過是擴大了同步范圍嫌松,效率更低了沪曙。
synchronized出現在實例方法上,一定鎖的是this萎羔。沒得挑液走,只能是this。不能是其它對象了贾陷。所以這種方式不靈活缘眶。
另外還有一個缺點:synchronized出現在實例方法上表示整個方法體都需要同步,可能會無故擴大同步的范圍髓废,導致程序的執(zhí)行效率降低巷懈。所以這種方式不常用。
synchronized使用在實例方法上有什么優(yōu)點慌洪?
代碼寫的少了顶燕。節(jié)儉了凑保。
如果共享的對象就是this,并且需要同步的代碼塊是整個方法體涌攻,建議使用這種方式欧引。
如果使用局部變量的話:
建議使用:StringBuilder。
因為局部變量不存在線程安全問題恳谎。選擇StringBuilder芝此。StringBuffer效率比較低。
ArrayList是非線程安全的惠爽。
Vector是線程安全的癌蓖。
HashMap HashSet是非線程安全的。
Hashtable是線程安全的婚肆。
synchronized的三種寫法
第一種:同步代碼塊
靈活
synchronized(線程共享對象){
同步代碼塊;
}
第二種:在實例方法上使用synchronized
表示共享對象一定是this
并且同步代碼塊是整個方法體租副。
第三種:在靜態(tài)方法上使用synchronized
表示找類鎖。
類鎖永遠只有1把较性。
就算創(chuàng)建了100個對象用僧,那類鎖也只有一把。
對象鎖:1個對象一把鎖赞咙,100個對象100把鎖责循。
類鎖:100個對象,也可能只是1把類鎖攀操。
面試題
//面試題:doOther方法執(zhí)行的時候需要等待doSome方法的結束么院仿?
//不需要。因為doOther()方法沒有synchronized
public class MianShiTi1 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//這個睡眠的作用是:為了保證t1線程先執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
public void run(){
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
//synchronized出現在實例方法上速和,表示鎖this歹垫。
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
//面試題:doOther方法執(zhí)行的時候需要等待doSome方法的結束么?
//需要颠放。
public class MianShiTi1 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//這個睡眠的作用是:為了保證t1線程先執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
public void run(){
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
//synchronized出現在實例方法上排惨,表示鎖this。
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
//面試題:doOther方法執(zhí)行的時候需要等待doSome方法的結束么碰凶?
//不需要暮芭。因為MyClass的對象是兩個,兩把鎖
public class MianShiTi1 {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//這個睡眠的作用是:為了保證t1線程先執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
public void run(){
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
//synchronized出現在實例方法上欲低,表示鎖this辕宏。
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
//面試題:doOther方法執(zhí)行的時候需要等待doSome方法的結束么?
//需要砾莱。因為靜態(tài)方法是類鎖匾效,不管創(chuàng)建了幾個對象,類鎖只有一把
public class MianShiTi1 {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);//這個睡眠的作用是:為了保證t1線程先執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
public void run(){
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
//synchronized出現在靜態(tài)方法上恤磷,表示類鎖面哼。
public synchronized static void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized static void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
死鎖
/*
死鎖代碼要會寫野宜。
一般面試官要求你會寫。
只有會寫的魔策,才會在以后的開發(fā)中注意這個事兒匈子。
因為死鎖很難調試
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
//t1和t2兩個線程共享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){
}
}
}
}
synchronized在開發(fā)中最好不要嵌套使用。一不小心就可能會導致死鎖現象的發(fā)生闯袒。
開發(fā)中應該怎么解決線程安全問題虎敦?
難道一上來就選擇線程同步嗎?synchronized
不是政敢,synchronized 會讓程序的執(zhí)行效率降低其徙,用戶體驗不好。系統(tǒng)的用戶吞吐量降低喷户。用戶體驗差唾那。在不得已的情況下再選擇線程同步機制。
第一種方案:盡量使用局部變量代替“實例變量和靜態(tài)變量”褪尝。
第二種方案:如果必須是實例變量闹获,那么可以考慮創(chuàng)建多個對象,這樣實例變量的內存就不共享了河哑。(一個線程對應一個對象避诽,100個線程對應100個對象。對象不共享璃谨,就沒有數據安全問題了沙庐。)
第三種方案:如果不能使用局部變量,對象也不能創(chuàng)建多個佳吞,這個時候就只能選擇synchronized 了拱雏。線程同步機制。
守護線程
java語言中線程分為兩大類:
一類是:用戶線程
一類是:守護線程
其中具有代表性的就是:垃圾回收線程(守護線程)容达。
守護線程的特點:
一般守護線程是一個死循環(huán),所有的用戶線程只要結束垂券,守護線程自動結束花盐。
注意:主線程main方法是一個用戶線程。
守護線程用在什么地方呢菇爪?
每天00:00的時候系統(tǒng)數據自動備份算芯。
這個需要使用到定時器,并且我們可以將定時器設置為守護線程凳宙。一直在那里看著熙揍,每到00:00的時候就備份一次。所有的用戶線程如果結束了氏涩,守護線程自動退出届囚,沒有必要進行數據備份了有梆。
將線程設置為守護線程
t.setDaemon(true);
public class ShouHu {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("備份數據的線程");
//啟動線程之前,將線程設置為守護線程
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)意系,但由于該線程是守護者泥耀,當用戶進程結束,守護線程自動終止蛔添。
while (true){
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定時器
定時器的作用:
間隔特定的時間痰催,執(zhí)行特定的程序。
每周要進行銀行賬戶的總賬操作迎瞧。
每天要進行數據的備份操作夸溶。
在實際的開發(fā)中,每隔多久執(zhí)行一段特定的程序凶硅,這種需求是很常見的缝裁,那么在java中其實可以采用多種方式實現:
可以使用sleep方法,睡眠咏尝,設置睡眠時間压语,每到這個時間點醒來,執(zhí)行任務编检。這種方式是最原始的定時器胎食。(比較low)
在java的類庫中已經寫好了一個定時器:java.util.Timer,可以直接拿來用允懂。
不過厕怜,這種方式在目前的開發(fā)中也很少用,因為現在有很多高級框架都是支持定時任務的蕾总。
在實際的開發(fā)中粥航,目前使用較多的是Spring框架中提供的SpringTask框架,這個框架只要進行簡單的配置生百,就可以完成定時器的任務递雀。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class DingShi {
public static void main(String[] args) throws Exception{
//創(chuàng)建定時器對象
Timer timer = new Timer();
//Timer timer = new Timer(true);//守護線程的方式
//指定定時任務
//timer.schedule(定時任務,第一次執(zhí)行時間蚀浆,間隔多久執(zhí)行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firsteTime = sdf.parse("2020-11-15 10:00:00");
timer.schedule(new logTimerTask(),firsteTime,1000 * 10);
//每年執(zhí)行一次
//timer.schedule(new logTimerTask(),firsteTime,1000L * 60L * 60L * 24L * 365L);
//匿名內部類方式也是可以的
timer.schedule(new TimerTask() {
@Override
public void run() {
//code....
}
},firsteTime,1000 * 10);
}
}
//編寫一個定時任務類
//假設這是一個記錄日志的定時任務
class logTimerTask extends TimerTask{
@Override
public void run() {
//編寫你需要執(zhí)行的任務就行了
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ":完成了一次數據備份缀程!");
}
}
實現線程的第三種方式
實現Callable接口。
這種方式實現的線程可以獲取線程的返回值市俊。
之前學的那兩種方式是無法獲取 線程的返回值的杨凑,因為run方法返回void。
系統(tǒng)委派一個線程去執(zhí)行一個任務摆昧,該線程執(zhí)行完任務之后撩满,可能會有一個執(zhí)行結果,我們怎么能拿到這個執(zhí)行結果呢?
使用第三種方式:實現Callable接口方式伺帘。
這種方式的優(yōu)點:可以獲取到線程的執(zhí)行結果昭躺。
這種方式的缺點:效率比較低,在獲取t線程執(zhí)行結果的時候曼追,當前線程受阻塞窍仰,效率較低。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;//JUC包下的礼殊,屬于java的并發(fā)包驹吮,老JDK中沒有這個包。新特性晶伦。
public class Text {
public static void main(String[] args) throws Exception{
//第一步:創(chuàng)建一個“未來任務類”對象
//參數非常重要碟狞,需要給一個Callable接口實現對象
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call()方法就相當于run方法。只不過這個有返回值
//線程執(zhí)行一個任務婚陪,執(zhí)行之后可能會有一個執(zhí)行結果
//模擬執(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;//自動裝箱(300結果變成Integer)
}
});
//創(chuàng)建線程對象
Thread t = new Thread(task);
//啟動線程
t.start();
//這里是main方法族沃,這是在主線程中。
//在主線程中泌参,怎么獲取t線程的返回結果脆淹?
//get()方法執(zhí)行會導致“當前線程阻塞”
Object obj = task.get();
System.out.println("線程執(zhí)行結果" + obj);
//main方法這里的程序要想執(zhí)行必須等待get()方法的結束
//而get()方法可能需要很久。因為get()方法是為了拿另一個線程的執(zhí)行結果
//另一個線程執(zhí)行是需要時間的
System.out.println("hello world");
}
}
關于Object類中的wait和notify方法
第一:wait和notify方法不是線程對象的方法沽一,是java中任何一個java對象都有的方法盖溺,因為這兩個方法是Object類中自帶的。
wait方法和notify方法不是通過線程對象調用的铣缠。
第二:wait方法作用烘嘱?
Object o = new Object();
o.wait();
表示:讓正在o對象上活動的線程進入等待狀態(tài),無期限等待蝗蛙,直到被喚醒為止蝇庭。
o.wait();方法的調用,會讓讓“當前線程”(正在o對象上活動的線程)進入等待狀態(tài)捡硅。
第三:notify方法作用哮内?
Object o = new Object();
o.notify();
表示:喚醒整在o對象上等待的線程。
還有一個notifyAll()方法:
這個方法是喚醒o對象上出于等待的所有線程壮韭。
生產者和消費者模式
使用wait方法和notify方法實現“生產者和消費者模式”
什么是“生產者和消費者模式”北发?
生產線程負責生產,消費線程負責消費泰涂。
生產線程和消費線程要達到均衡鲫竞。
這是一種特殊的業(yè)務需求辐怕,在這種特殊的情況下需要使用wait方法和notify方法逼蒙。
wait和notify方法不是線程對象的方法,是最普通java對象都有的方法寄疏。
wait方法和notify方法建立在線程同步的基礎之上是牢。因為多線程要同時操作一個倉庫僵井。有線程安全問題。
wait方法作用:o.wait();讓正在o對象上活動的線程t進入等待狀態(tài)驳棱,并且釋放掉t線程之前占有的o對象的鎖批什。
notify方法作用:o.notify();讓正在o對象上等待的線程喚醒,只是通知社搅,不會釋放o對象上之前占有的鎖驻债。
···
/*
模擬這樣一個要求:
倉庫我們采用List集合。
List集合中假設只能存儲1個元素形葬。
1個元素就表示倉庫滿了合呐。
如果List集合中元素個數是0,就表示倉庫空了笙以。
保證List集合中永遠都是最多存儲1個元素淌实。
必須做到這種效果:生產1個,消費1個猖腕。
*/
public class ThreadTest16 {
public static void main(String[] args) {
// 創(chuàng)建1個倉庫對象拆祈,共享的。
List list = new ArrayList();
// 創(chuàng)建兩個線程對象
// 生產者線程
Thread t1 = new Thread(new Producer(list));
// 消費者線程
Thread t2 = new Thread(new Consumer(list));
t1.setName("生產者線程");
t2.setName("消費者線程");
t1.start();
t2.start();
}
}
// 生產線程
class Producer implements Runnable {
// 倉庫
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直生產(使用死循環(huán)來模擬一直生產)
while(true){
// 給倉庫對象list加鎖倘感。
synchronized (list){
if(list.size() > 0){ // 大于0放坏,說明倉庫中已經有1個元素了。
try {
// 當前線程進入等待狀態(tài)侠仇,并且釋放Producer之前占有的list集合的鎖轻姿。
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能夠執(zhí)行到這里說明倉庫是空的,可以生產
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 喚醒消費者進行消費
list.notifyAll();
}
}
}
}
// 消費線程
class Consumer implements Runnable {
// 倉庫
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直消費
while(true){
synchronized (list) {
if(list.size() == 0){
try {
// 倉庫已經空了逻炊。
// 消費者線程等待互亮,釋放掉list集合的鎖
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能夠執(zhí)行到此處說明倉庫中有數據,進行消費余素。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 喚醒生產者生產豹休。
list.notifyAll();
}
}
}
}
···