基本線程類(lèi)
1. 繼承Thread類(lèi)
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
Thread類(lèi)本質(zhì)上是實(shí)現(xiàn)Runnable接口的一個(gè)實(shí)例,代表一個(gè)線程的實(shí)例。啟動(dòng)線程的唯一方法是通過(guò)Thread類(lèi)的start()實(shí)例方法。start()是一個(gè)native方法,它將啟動(dòng)一個(gè)新線程,并執(zhí)行run()方法。這種方式實(shí)現(xiàn)多線程很簡(jiǎn)單,通過(guò)自己的類(lèi)直接extend Thread,并復(fù)寫(xiě)run()方法,就可以啟動(dòng)新線程并執(zhí)行自己定義的run()方法。 **多個(gè)線程之間無(wú)法共享線程類(lèi)的實(shí)例變量 **
2.實(shí)現(xiàn)Runnable接口
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
3.實(shí)現(xiàn)
Callable接口通過(guò)FutureTask包裝器來(lái)創(chuàng)建線程
Callable接口提供了一個(gè)call()方法可以作為線程執(zhí)行體,這個(gè)方法具有返回值,還可以聲明拋出異常
實(shí)現(xiàn)Callable接口通過(guò)FutureTask包裝器來(lái)創(chuàng)建Thread線程future模式 并發(fā)模式的一種, 可以有二種形式, 無(wú)阻賽和阻塞,分別是isDone和get其中future 對(duì)象用來(lái)存放該線程的返回值以及狀態(tài)
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
public class SomeCallable<V> extends OtherClass implements Callable<V> {
@Override
public V call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Callable<V> oneCallable = new SomeCallable<V>();
//由Callable<Integer>創(chuàng)建一個(gè)FutureTask<Integer>對(duì)象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);
//注釋?zhuān)篎utureTask<Integer>是一個(gè)包裝器,它通過(guò)接受Callable<Integer>來(lái)創(chuàng)建,
//它同時(shí)實(shí)現(xiàn)了Future和Runnable接口。
//由FutureTask<Integer>創(chuàng)建一個(gè)Thread對(duì)象:
Thread oneThread = new Thread(oneTask);
oneThread.start(); //至此,一個(gè)線程就創(chuàng)建完成了
4.使用
ExccutorService,Callable,Future實(shí)現(xiàn)有返回結(jié)果的線程
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable();
// 執(zhí)行任務(wù)并獲取Future對(duì)象
Future f = pool.submit(c);
list.add(f);
}
// 關(guān)閉線程池
pool.shutdown();
//從Future對(duì)象上獲取任務(wù)的返回值
f.get()
// 獲取所有并發(fā)任務(wù)的運(yùn)行結(jié)果
//實(shí)現(xiàn)Callable接口
class MyCallable implements Callable<Object> {
public Object call() throws Exception {
return null;
}
-
ExecutoreService提供了submit()方法,傳遞一個(gè)Callable,或Runnable,返回Future。如果Executor后臺(tái)線程池還沒(méi)有完成Callable的計(jì)算,這調(diào)用返回Future對(duì)象的get()方法,會(huì)阻塞直到計(jì)算完成
ExecutorService e = Executors.newFixedThreadPool(taskSize); //新建線程池
//submit方法有多重參數(shù)版本,及支持callable也能夠支持runnable接口類(lèi)型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 無(wú)阻塞
future.get() // return 返回值,阻塞直到該線程運(yùn)行結(jié)束
總結(jié) :Runnable,Callable接口與Thread類(lèi)的對(duì)比
- 采用實(shí)現(xiàn)
Runnable,Callable接口優(yōu)缺點(diǎn):
1,接口可以多繼承,繼承了Runnable接口還能繼承其他接口
2,適合多個(gè)相同線程來(lái)處理同一份資源的情況,
3,缺點(diǎn)是,編程稍微復(fù)雜,訪問(wèn)當(dāng)前線程必須使用Thread.currentThread() - 采用繼承
Thread類(lèi)優(yōu)缺點(diǎn):
1,編寫(xiě)簡(jiǎn)單,訪問(wèn)當(dāng)前線程可直接用this
2,缺點(diǎn)是,不能再繼承其他類(lèi)
綜上,建議采用實(shí)現(xiàn)Runnable接口的方法來(lái)創(chuàng)建和啟動(dòng)線程
synchronized關(guān)鍵字
synchronized關(guān)鍵字用于修飾方法和代碼塊,以實(shí)現(xiàn)同步,當(dāng)多個(gè)線程在執(zhí)行被synchronized修飾的代碼,以排隊(duì)的方式進(jìn)行處理。當(dāng)一個(gè)線程調(diào)用被修飾的代碼時(shí),先判斷有沒(méi)有被上鎖,如果上鎖就說(shuō)明有其他線程在調(diào)用,必須等待其他線程結(jié)束調(diào)用后才能執(zhí)行這段代碼,synchronized可以在任意對(duì)象以及方法上加鎖,加鎖的這段代碼被稱(chēng)為“互斥區(qū)”或者“臨界區(qū)”
synchronized關(guān)鍵字加到static靜態(tài)方法是給Class類(lèi)上鎖
synchronized關(guān)鍵字加到非static靜態(tài)方法上是給對(duì)象上鎖
關(guān)于并行,并發(fā)和同步的概念
- 并行:
- 多個(gè)CPU實(shí)例或者多臺(tái)機(jī)器同時(shí)執(zhí)行一段處理邏輯,是真正的同時(shí).
- 并發(fā)
- 通過(guò)CPU調(diào)度算法,讓用戶看上去同時(shí)執(zhí)行,實(shí)際上CPU操作層面不是真正的同時(shí),并發(fā)往往場(chǎng)景中有公共資源,那么針對(duì)這個(gè)公用的資源往往產(chǎn)生瓶頸,我們會(huì)用TPS或者QPS來(lái)反應(yīng)這個(gè)系統(tǒng)的處理能力
- 同步
- Java中的同步指的是通過(guò)人為的控制和調(diào)度,保證共享資源的多線程訪問(wèn)成為線程安全,.來(lái)確保結(jié)果的準(zhǔn)確,如上面的
線程安全
- 指在并發(fā)的情況下,改代碼經(jīng)過(guò)多線程的使用,線程的調(diào)度順序不影響任何結(jié)果,這個(gè)時(shí)候使用多線程,我們只需要關(guān)注系統(tǒng)功能的內(nèi)存,Cpu是不是夠用即可,反過(guò)來(lái),線程不安全就意味著線程的調(diào)度順序會(huì)響應(yīng)最終結(jié)果
- 多個(gè)線程同時(shí)操作一個(gè)全局變量是不安全的,使用自旋鎖并不是絕對(duì)的安全(因?yàn)閱螌?xiě)多讀)。
- 在多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí),仍然能夠保證數(shù)據(jù)的正確 。使用互斥鎖可以實(shí)現(xiàn),但是消耗性能
- 所有更新UI的操作都在主線程上執(zhí)行
線程的狀態(tài)
新建狀態(tài):使用
new關(guān)鍵字和Thread類(lèi)或其子類(lèi)建立一個(gè)線程對(duì)象后,該線程對(duì)象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序start()這個(gè)線程。就緒狀態(tài):當(dāng)線程對(duì)象調(diào)用了
start()方法之后,該線程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度。阻塞(Blocked) : 對(duì)Running狀態(tài)的線程加同步鎖
synchronized使其今如(Lock Blocked Pool)同步鎖被釋放今如可運(yùn)行狀態(tài),等待(Waiting) :線程可以主動(dòng)調(diào)用
obj.wait或者Thread.sleep或者 join 今入,Waiting是等待另一個(gè)線程完成某一個(gè)操作,如join等待另一個(gè)完成執(zhí)行.waiting 和 Blocked狀態(tài), Blocked也是一種等待,等待的是monitor 但是waiting等待是notify()方法
每個(gè)對(duì)象都有的機(jī)制
- monitor :Java中 每個(gè)對(duì)象都有一個(gè)監(jiān)視器,來(lái)監(jiān)視并發(fā)代碼的重入,在非多線程編碼時(shí)改監(jiān)視器不發(fā)揮作用,反之如果在``synchronized`范圍內(nèi),監(jiān)視器發(fā)揮作用.
- wait/notify 必須存在于
synchronized塊中, 并且這三個(gè)關(guān)鍵字針對(duì)的是同一個(gè)監(jiān)視器,意味著wait之后,其他線程可以進(jìn)入同步代碼塊 - synchronized 單獨(dú)使用 在多線程環(huán)境下,
synchronized塊中的方法獲取了lock實(shí)例的monitor,如果實(shí)例相同,那么只有一個(gè)線程能執(zhí)行該塊內(nèi)容
優(yōu)雅地停止線程
interrupt阻塞中斷和非阻塞中斷interrupt()方法并不會(huì)立即執(zhí)行中斷操作,這個(gè)方法只會(huì)給線程設(shè)置一個(gè)為true的中斷標(biāo)志。設(shè)置之后,則根據(jù)線程當(dāng)前的狀態(tài)進(jìn)行不同的后續(xù)操作。-
線程的當(dāng)前狀態(tài)處于非阻塞狀態(tài),那么僅僅是線程的中斷標(biāo)志被修改為true而已(2)如果線程的當(dāng)前狀態(tài)處于阻塞狀態(tài),那么在將中斷標(biāo)志設(shè)置為true后,如果是 wait、sleep以及join 三個(gè)方法引起的阻塞,那么會(huì)將線程的中斷標(biāo)志重新設(shè)置為false,并拋出一個(gè)
InterruptedException,這樣受阻線程就得以退出阻塞的狀態(tài)。public class TestThread1 { public static void main(String[] args) { MyRunnable1 myRunnable=new MyRunnable1(); Thread thread=new Thread(myRunnable,"子線程"); thread.start(); try{ //主線程休眠 Thread.sleep(3000); //調(diào)用中斷,true thread.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyRunnable1 implements Runnable{ @Override public void run() { int i=0; while(true){ System.out.println(Thread.currentThread().getName()+"循環(huán)第"+ ++i+"次"); try{ //判斷線程的中斷情況 boolean interruptStatus=Thread.currentThread().isInterrupted(); System.out.println(Thread.currentThread().getName()+"循環(huán)第"+ ++i+"次"+interruptStatus); Thread.sleep(1000); //非阻塞中斷 只是設(shè)置標(biāo)記位true //非阻塞中斷 只是設(shè)置標(biāo)記位true if(interruptStatus){ //如果中斷為true則退出 break; } } catch (InterruptedException e) { // 一個(gè)線程在運(yùn)行狀態(tài)中,其中斷標(biāo)志被設(shè)置為true之后,一旦線程調(diào)用了 // wait、join、sleep方法中的一種,立馬拋出一個(gè)InterruptedException,且中斷標(biāo)志被程序會(huì)自動(dòng)清除,重新設(shè)置為false System.out.println("阻塞中斷"+Thread.currentThread().isInterrupted());//顯示false并拋異常 return;//不想返回還可繼續(xù)寫(xiě)代碼 } } } } 子線程循環(huán)第1次false 子線程循環(huán)第2次false 子線程循環(huán)第3次false 阻塞中斷false -
Stop()停止- 由于不安全,已經(jīng)不使用了,因?yàn)?code>stop會(huì)解除由線程獲取的所有鎖定,當(dāng)在一個(gè)線程對(duì)象上調(diào)用
stop()方法時(shí),這個(gè)線程對(duì)象所運(yùn)行的線程就會(huì)立即停止,假如一個(gè)線程正在執(zhí)行:synchronized void { x = 3; y = 4;}由于方法是同步的, 多個(gè)線程訪問(wèn)時(shí)總能保證x,y被同時(shí)賦值,而如果一個(gè)線程正在執(zhí)行到x = 3;時(shí),被調(diào)用了 stop()方法,即使在同步塊中,它也會(huì)馬上stop了,這樣就產(chǎn)生了不完整的殘廢數(shù)據(jù)。
- 由于不安全,已經(jīng)不使用了,因?yàn)?code>stop會(huì)解除由線程獲取的所有鎖定,當(dāng)在一個(gè)線程對(duì)象上調(diào)用
設(shè)置標(biāo)記位停止
public class TestThread2_1 {
public static void main(String[] args) throws InterruptedException {
MyThreads my = new MyThreads();
new Thread(my, "線程A").start();
Thread.sleep(10000);
//設(shè)置標(biāo)記位
//my.setFlag(false);
//stop方法
new Thread(my, "線程A").stop();
System.out.println("代碼結(jié)束");
}
}
class MyThreads implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
try {
Thread.sleep(1000);
System.out.println("第" + i + "次執(zhí)行,線程名稱(chēng)為:" + Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
多線程控制類(lèi)
ThreadLocal類(lèi)(關(guān)于這個(gè)類(lèi)后續(xù)會(huì)推出關(guān)于這個(gè)類(lèi)的專(zhuān)題)
用處: 保存線程的獨(dú)立變量, 對(duì)一個(gè)下線程類(lèi)使用
ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程單獨(dú)提供了獨(dú)立的變量,所以每個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其他線程所對(duì)應(yīng)的副本,長(zhǎng)用于用戶登錄控制,如記錄session信息-
常用方法
方法 作用 initValue() 副本創(chuàng)建的方法 get() 得到副本 set() 設(shè)置副本 -
實(shí)例
-
ThreadLocal能實(shí)現(xiàn)為不同的線程保存變量的原理是,它內(nèi)部有個(gè)Entry,保存<線程名,變量值>,不同的線程對(duì)應(yīng)著不同的value,就能加以區(qū)分了
public class ThreadLocalDemo { //銀行對(duì)象,有錢(qián),有存款和取款兩個(gè)操作 static class Bank{ ThreadLocal<Float> threadLocal = new ThreadLocal<Float>() { protected Float initialValue() { return 0.0f; } }; //存款 public Float get() { return threadLocal.get(); } //取款 public void set(Float money) { threadLocal.set(threadLocal.get()+money); } } //轉(zhuǎn)賬對(duì)象,從銀行中取錢(qián)然后轉(zhuǎn)賬,然后保存到賬戶 static class Transfer implements Runnable{ private Bank bank; public Transfer(Bank bank){ this.bank = bank; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<10;i++) { bank.set(10.0f); System.out.println(Thread.currentThread().getName()+"賬戶余額"+bank.get()); } } } } //main 中 模擬操作 Bank bank = new Bank(); Transfer transfer = new Transfer(bank); Thread t1 =new Thread(transfer,"客戶1"); Thread t2 =new Thread(transfer,"客戶2"); t1.start(); t2.start(); -
Lock類(lèi)
-
Lock類(lèi)實(shí)際上是一個(gè)接口,我們?cè)趯?shí)例化的時(shí)候?qū)嶋H上是實(shí)例化實(shí)現(xiàn)了該接口的類(lèi)
Lock lock = new ReentrantLock();
-
作用 :通過(guò)Lock對(duì)象lock,用
lock.lock來(lái)加鎖 用lock.unlock來(lái)釋放鎖。在兩者中間放置需要同步處理的代碼。- 可重入鎖(
ReentrantLock): 線程請(qǐng)求它已經(jīng)擁有的鎖時(shí)不會(huì)阻塞,可以進(jìn)入它已經(jīng)擁有的同步代碼塊,但是它擁有了多少次,就要解鎖多少次才能釋放鎖 - 讀寫(xiě)鎖(
ReadWriteLock) :可以同時(shí)讀,但是讀的時(shí)候不能寫(xiě),也不能同時(shí)寫(xiě),寫(xiě)的時(shí)候不能讀
public class MyConditionService { private Lock lock = new ReentrantLock();//ReentrantLock是個(gè)重入鎖 public void testMethod(){ lock.lock();//加鎖 for (int i = 0 ;i < 5;i++){ System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1))); } lock.unlock();//解鎖 } } - 可重入鎖(
-
其他作用
- 實(shí)現(xiàn)鎖的公平
- 獲取當(dāng)前線程調(diào)用lock的次數(shù),也就是獲取當(dāng)前線程鎖定的個(gè)數(shù)
- 獲取等待鎖的線程數(shù)
- 查詢(xún)指定的線程是否等待獲取此鎖定
- 查詢(xún)是否有線程等待獲取此鎖
- 查詢(xún)當(dāng)前線程是否持有鎖定
- 判斷一個(gè)鎖是不是被線程持有
- 加鎖時(shí)如果中斷則不加鎖,進(jìn)入異常處理
- 嘗試加鎖,如果該鎖未被其他線程持有的情況下成功
Condition類(lèi)
Condition是Java提供了來(lái)實(shí)現(xiàn)等待/通知的類(lèi),Condition類(lèi)還提供比wait/notify更豐富的功能,Condition對(duì)象是由lock對(duì)象所創(chuàng)建的。但是同一個(gè)鎖可以創(chuàng)建多個(gè)Condition的對(duì)象,即創(chuàng)建多個(gè)對(duì)象監(jiān)視器。這樣的好處就是可以指定喚醒線程。notify喚醒的線程是隨機(jī)喚醒一個(gè)。
condition對(duì)象通過(guò)
lock.newCondition()來(lái)創(chuàng)建,用condition.await()來(lái)實(shí)現(xiàn)讓線程等待,使得線程進(jìn)入阻塞。用condition.signal()來(lái)實(shí)現(xiàn)喚醒線程。喚醒的線程是用同一個(gè)conditon對(duì)象調(diào)用await()方法而進(jìn)入阻塞。并且和wait/notify一樣,await()和signal()也是在同步代碼區(qū)內(nèi)執(zhí)行。
對(duì)于等待/通知機(jī)制,簡(jiǎn)化而言,就是等待一個(gè)條件,當(dāng)條件不滿足時(shí),就進(jìn)入等待,等條件滿足時(shí),就通知等待的線程開(kāi)始執(zhí)行。為了實(shí)現(xiàn)這種功能,需要進(jìn)行wait的代碼部分與需要進(jìn)行通知的代碼部分必須放在同一個(gè)對(duì)象監(jiān)視器里面。執(zhí)行才能實(shí)現(xiàn)多個(gè)阻塞的線程同步執(zhí)行代碼,等待與通知的線程也是同步進(jìn)行。對(duì)于wait/notify而言,對(duì)象監(jiān)視器與等待條件結(jié)合在一起 即synchronized(對(duì)象)利用該對(duì)象去調(diào)用wait以及notify。但是對(duì)于``Condition類(lèi),是對(duì)象監(jiān)視器與條件分開(kāi),Lock類(lèi)來(lái)實(shí)現(xiàn)對(duì)象監(jiān)視器,condition`對(duì)象來(lái)負(fù)責(zé)條件,去調(diào)用await以及signal。-
示例代碼:
public class ConditionWaitNotifyService { private Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public void await(){ try{ lock.lock(); System.out.println("await的時(shí)間為 " + System.currentTimeMillis()); condition.await(); System.out.println("await結(jié)束的時(shí)間" + System.currentTimeMillis()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void signal(){ try{ lock.lock(); System.out.println("sign的時(shí)間為" + System.currentTimeMillis()); condition.signal(); }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { ConditionWaitNotifyService service = new ConditionWaitNotifyService(); new Thread(service::await).start(); Thread.sleep(1000 * 3); service.signal(); Thread.sleep(1000); } }- 其他作用:和wait類(lèi)提供了一個(gè)最長(zhǎng)等待時(shí)間,
awaitUntil(Date deadline)在到達(dá)指定時(shí)間之后,線程會(huì)自動(dòng)喚醒。但是無(wú)論是await或者awaitUntil,當(dāng)線程中斷時(shí),進(jìn)行阻塞的線程會(huì)產(chǎn)生中斷異常。Java提供了一個(gè)awaitUninterruptibly的方法,使即使線程中斷時(shí),進(jìn)行阻塞的線程也不會(huì)產(chǎn)生中斷異常。
- 其他作用:和wait類(lèi)提供了一個(gè)最長(zhǎng)等待時(shí)間,