Thread
多線程為我們解決了程序中需要并發(fā)執(zhí)行多個任務(wù)的操作隘擎,可以通過創(chuàng)建一個線程來負(fù)責(zé)執(zhí)行需要執(zhí)行的任務(wù)。創(chuàng)建線程的方式有兩種:
1:創(chuàng)建一個類來繼承Thread類货葬,在類中重寫需要執(zhí)行對應(yīng)操作的run方法
class MyThread extends Thread {
public void run(){
for(int i=0;i<1000;i++){
system.out.println(11111);
}
}
}
//利用造型創(chuàng)建Thread
public static void main(String[] args){
Thread t1 = new MyThread();
}
t1.start();
需要注意的是,啟動線程需要調(diào)用Thread的start方法休傍,而不是重寫的run方法蹲姐,run方法中定義了該線程需要具體做的事情,當(dāng)start方法調(diào)用后忙厌,線程納入線程調(diào)度江咳,線程調(diào)度會自動分配CPU時間片來執(zhí)行線程,線程運行時會自動調(diào)用run方法。另外婶芭,run方法是在start方法調(diào)用完畢后執(zhí)行的着饥,而不是start方法運行過程中執(zhí)行。
這種創(chuàng)建方法可以匿名類來實現(xiàn)宰掉,相對簡單
Thread t2 = new Thread(){
public void run(){
system.out.println("具體方法");
}
}
這種線程創(chuàng)建方式由于以下兩點不足所以不推薦使用:
a,由于Java是單繼承的孟害,所以當(dāng)當(dāng)前類繼承了Thread類時就不能繼承其他父類來實現(xiàn)重用挪拟,如果不繼承Thread就不能作為單一線程并發(fā),這里出現(xiàn)了繼承沖突玉组。
b谎柄,由于線程類要執(zhí)行操作就必須重寫run方法,導(dǎo)致線程和執(zhí)行的任務(wù)出現(xiàn)了強耦合關(guān)系惯雳,該線程只能執(zhí)行該任務(wù)朝巫,不利于我們后續(xù)對此線程的復(fù)用,要知道我們不可能有多少任務(wù)就開多少線程石景,要盡可能的保證效率的同時減少線程數(shù)量劈猿。
2:單獨創(chuàng)建Runnable接口并重寫run方法來單獨定義任務(wù),并通過Thread實例來實現(xiàn)Runnable接口潮孽,實現(xiàn)線程和任務(wù)的解耦
Runnable r1 = new Runnable(){
public void run(){
system.out.println("任務(wù)一");
}
}
Runnable r2 = new Runnable(){
public void run(){
system.out.println("任務(wù)二");
}
}
Thread t = new Thread(r1);
Thread t1 = new Thread(r2);
t.start();
t1.start();
獲取當(dāng)前線程
Java當(dāng)中所有的程序都是跑在線程上的揪荣,Java提供了一個靜態(tài)方法來獲取這個線程
static Thread currentThread()
在哪個方法中運行該方法就會獲取運行該方法的線程,main方法也是靠線程運行恩商,其過程是:當(dāng)程序啟動時变逃,操作系統(tǒng)創(chuàng)建一個進(jìn)程來運行虛擬機(jī)必逆,進(jìn)程運行后會創(chuàng)建一個線程來運行main方法怠堪,進(jìn)程中至少需要一個線程。
Thread t = Thread.currentThread();
System.out.println(t);//Thread[main,5,main]
獲取線程相關(guān)信息的API
Thread main = Thread.currentThread();
//線程ID
long id = main.getId();
//線程名
String name = main.getName();
//優(yōu)先級
int priority = main.getPriority();
//是否為守護(hù)線程
boolean isDaemon = main.isDaemon();
//是否活著
boolean isAlive = main.isAlive();
//是否被中斷
boolean isInterrupted = main.isInterrupted();
線程優(yōu)先級
線程不能干預(yù)線程調(diào)度的工作名眉,即線程無法決定時間片分配給哪個線程粟矿,也不能決定分配的具體次數(shù),但可以通過更改線程優(yōu)先級來提高分配到CPU時間片的幾率损拢,線程優(yōu)先級分為10個等級陌粹,最低為1掏秩,默認(rèn)5蒙幻,最高10邮破,理論上優(yōu)先級越高獲取CPU時間片的幾率越大矫渔。
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0;i <10; i++){
Thread t = Thread.currentThread();
t.setPriority(Thread.MAX_PRIORITY);
System.out.println("max");
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0;i <10; i++){
Thread t = Thread.currentThread();
t.setPriority(Thread.NORM_PRIORITY);
System.out.println("normal");
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0;i <10; i++){
Thread t = Thread.currentThread();
t.setPriority(Thread.MAX_PRIORITY);
System.out.println("min");
}
}
}).start();
}
守護(hù)線程
守護(hù)線程又叫做后臺線程镊辕,當(dāng)一個進(jìn)程中的所有前臺線程都結(jié)束時丑蛤,進(jìn)程結(jié)束前會強制結(jié)束后臺線程碌补。比如Java中的GC就是一個典型的后臺線程厦章,程序運行過程中GC會定時檢查堆中是否有垃圾可以清理,當(dāng)前臺線程都結(jié)束時GC也會停止工作群发。
Thread t = new Thread(){
public void run(){
System.out.println("這里為后臺線程任務(wù)");
}
};
//在啟動線程前設(shè)置為后臺線程
t.setDaemon(true);
t.start();
多線程并發(fā)的安全問題
同步鎖
當(dāng)一個程序中有很多線程的時候,會產(chǎn)生多個線程同時訪問一個方法的現(xiàn)象起愈,比如兩個人同時都想上廁所官觅,就會產(chǎn)生沖突,而程序當(dāng)中解決這個問題的方法和現(xiàn)實當(dāng)中一樣滑绒,就是排隊,而排隊的先決條件就是給廁所上一個鎖纵势,這樣即便有人想搶也進(jìn)不去。
我們利用synchronized關(guān)鍵字來給需要的方法或代碼塊上鎖才漆,例如一個方法被上鎖后就不允許多個線程同時執(zhí)行它,這個方法被稱為同步方法
class Bathroom {
public synchronized void goToBathroom(){
//假如一個人上廁所耗時5秒
Thread.sleep(5000);
System.out.println("廁所有人~");
}
}
public static void main(String[] args){
Bathroom bt = new Bathroom();
Thread person1 = new Thread(){
public void run(){
bt.goToBathroom();
}
};
Thread person2 = new Thread(){
public void run(){
bt.goToBathroom();
}
};
person1.start();
person2.start();
}
這兩個線程運行起來后線程調(diào)度會隨機(jī)分配時間片給他們阅虫,當(dāng)一個線程在5秒內(nèi)訪問方法的時候該方法就被上了鎖颓帝,其他線程無法訪問,只有訪問結(jié)束后其他線程才會訪問工猜。這樣就起到了排隊的效果拴泌。
有效縮小同步鎖的范圍可以在保證安全的前提下提高并發(fā)效率
比如我們?nèi)ド虉鲑I衣服箭昵,先挑選喜歡的衣服然后拿去試衣間試衣服,這兩個過程我們沒必要都排隊,只有試衣服的時候才會排隊并且關(guān)門上鎖涵但,在程序中可以這樣表示:
class Shopping{
Thread t = Thread.currentThread();
public void buy(){
System.out.println(t+"正在挑選衣服~");
Thread.sleep(5000);
synchronized(this){
System.out.println(t+"正在試衣服~請排隊~");
Thread.sleep(5000);
}
System.out.println(t+"結(jié)賬離開~");
}
}
public class test{
public static void main(String[] args){
Shopping s = new Shopping();
Thread person1 = new Thread(){
public void run(){
s.buy();
}
};
Thread person2 = new Thread(){
public void run(){
s.buy();
}
};
person1.start();
person2.start();
}
}
這樣做可以減少排隊時間塑娇,提高商場的運作效率埋涧,程序中同樣,當(dāng)許多線程同時要訪問一個方法而必須排隊時我們可以縮小同步范圍,只將方法中必須排隊執(zhí)行的代碼塊進(jìn)行上鎖次坡,而方法中其他代碼可以并發(fā)執(zhí)行,減少排隊時間淫僻。這里需要注意的是在給方法中代碼塊上鎖時要指定一個同步監(jiān)視器悯辙,通常情況下都可用this來指定击费,即當(dāng)前對象荡灾,要想同步代碼塊有效果必須保證多線程看到的鎖對象是同一個對象瓤狐。本例中同一個對象指代為同一個商場實例皆警,如果是不同的商場即不同的對象意推,也就不存在排隊或者搶的問題珊蟀,因為它們根本不搭嘎~
靜態(tài)方法的同步
當(dāng)我們給一個靜態(tài)方法加鎖的時候育灸,鎖對象就為當(dāng)前類的類對象儿子,當(dāng)JVM加載一個類的時候蒋譬,首先讀取該類的class文件儡毕,并同時創(chuàng)建一個類對象來保存該類的類信息腰湾,一個類有且只有一個類對象两残,所以靜態(tài)的同步方法一定有同步效果。
互斥鎖
互斥鎖是指最多只有一個線程可以持有的鎖。當(dāng)鎖對象相同時慈迈,如果該對象下有兩個方法或者兩段代碼以上都加了synchronized關(guān)鍵字若贮,代表這兩個方法或者代碼互斥,互斥意味著當(dāng)一個線程解鎖并執(zhí)行代碼時另外一段加鎖的代碼其他線程無法解鎖吩翻,也就無法同時運行兜看,只有當(dāng)該線程運行完畢歸還鎖后其他線程才能解鎖運行。這種情形通常出現(xiàn)在多線程訪問公共資源時狭瞎,為了避免資源被篡改或丟失细移,僅允許一個線程進(jìn)行訪問,而在訪問期間其他線程無法訪問熊锭。
class Test{
public synchronized void methodA(){
Thread t = Thread.currentThread();
System.out.println(t+"正在執(zhí)行A方法~");
Thread.sleep(5000);
System.out.println("A方法執(zhí)行完畢弧轧!");
}
public synchronized void methodB(){
Thread t = Thread.currentThread();
System.out.println(t+"正在執(zhí)行B方法~");
Thread.sleep(5000);
System.out.println("B方法執(zhí)行完畢雪侥!");
}
}
public class Lock{
public static void main(String[] args){
Test test = new Test();
Thread t1 = new Thread(){
public void run(){
test.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
test.methodB();
}
};
t1.start();
t2.start();
}
}