基本線程類
1. 繼承Thread類
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
類本質上是實現(xiàn)Runnable
接口的一個實例,代表一個線程的實例哥放。啟動線程的唯一方法是通過Thread
類的start()
實例方法亚皂。start()
是一個native
方法滓窍,它將啟動一個新線程,并執(zhí)行run()
方法醒颖。這種方式實現(xiàn)多線程很簡單朱躺,通過自己的類直接extend Thread
刁赖,并復寫run()
方法,就可以啟動新線程并執(zhí)行自己定義的run()
方法长搀。 **多個線程之間無法共享線程類的實例變量 **
2.實現(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.實現(xiàn)
Callable
接口通過FutureTask
包裝器來創(chuàng)建線程
Callable
接口提供了一個call()
方法可以作為線程執(zhí)行體,這個方法具有返回值鸡典,還可以聲明拋出異常
實現(xiàn)Callable
接口通過FutureTask
包裝器來創(chuàng)建Thread
線程future
模式 并發(fā)模式的一種, 可以有二種形式, 無阻賽和阻塞,分別是isDone
和get
其中future
對象用來存放該線程的返回值以及狀態(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)建一個FutureTask<Integer>對象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);
//注釋:FutureTask<Integer>是一個包裝器源请,它通過接受Callable<Integer>來創(chuàng)建,
//它同時實現(xiàn)了Future和Runnable接口彻况。
//由FutureTask<Integer>創(chuàng)建一個Thread對象:
Thread oneThread = new Thread(oneTask);
oneThread.start(); //至此谁尸,一個線程就創(chuàng)建完成了
4.使用
ExccutorService
,Callable
纽甘,Future
實現(xiàn)有返回結果的線程
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable();
// 執(zhí)行任務并獲取Future對象
Future f = pool.submit(c);
list.add(f);
}
// 關閉線程池
pool.shutdown();
//從Future對象上獲取任務的返回值
f.get()
// 獲取所有并發(fā)任務的運行結果
//實現(xiàn)Callable接口
class MyCallable implements Callable<Object> {
public Object call() throws Exception {
return null;
}
-
ExecutoreService
提供了submit()
方法良蛮,傳遞一個Callable
,或Runnable
悍赢,返回Future
决瞳。如果Executor
后臺線程池還沒有完成Callable
的計算,這調用返回Future
對象的get()
方法左权,會阻塞直到計算完成
ExecutorService e = Executors.newFixedThreadPool(taskSize); //新建線程池
//submit方法有多重參數(shù)版本皮胡,及支持callable也能夠支持runnable接口類型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 無阻塞
future.get() // return 返回值,阻塞直到該線程運行結束
總結 :Runnable
赏迟,Callable
接口與Thread
類的對比
- 采用實現(xiàn)
Runnable屡贺,Callable
接口優(yōu)缺點:
1,接口可以多繼承锌杀,繼承了Runnable接口還能繼承其他接口
2甩栈,適合多個相同線程來處理同一份資源的情況,
3糕再,缺點是量没,編程稍微復雜,訪問當前線程必須使用Thread.currentThread()
- 采用繼承
Thread
類優(yōu)缺點:
1亿鲜,編寫簡單允蜈,訪問當前線程可直接用this
2冤吨,缺點是,不能再繼承其他類
綜上饶套,建議采用實現(xiàn)Runnable
接口的方法來創(chuàng)建和啟動線程
synchronized關鍵字
synchronized
關鍵字用于修飾方法和代碼塊漩蟆,以實現(xiàn)同步,當多個線程在執(zhí)行被synchronized
修飾的代碼妓蛮,以排隊的方式進行處理怠李。當一個線程調用被修飾的代碼時,先判斷有沒有被上鎖蛤克,如果上鎖就說明有其他線程在調用捺癞,必須等待其他線程結束調用后才能執(zhí)行這段代碼,synchronized
可以在任意對象以及方法上加鎖构挤,加鎖的這段代碼被稱為“互斥區(qū)”或者“臨界區(qū)”
synchronized
關鍵字加到static
靜態(tài)方法是給Class
類上鎖
synchronized
關鍵字加到非static
靜態(tài)方法上是給對象上鎖
關于并行,并發(fā)和同步的概念
- 并行:
- 多個CPU實例或者多臺機器同時執(zhí)行一段處理邏輯,是真正的同時.
- 并發(fā)
- 通過CPU調度算法,讓用戶看上去同時執(zhí)行,實際上CPU操作層面不是真正的同時,并發(fā)往往場景中有公共資源,那么針對這個公用的資源往往產(chǎn)生瓶頸,我們會用TPS或者QPS來反應這個系統(tǒng)的處理能力
- 同步
- Java中的同步指的是通過人為的控制和調度,保證共享資源的多線程訪問成為線程安全,.來確保結果的準確,如上面的
線程安全
- 指在并發(fā)的情況下,改代碼經(jīng)過多線程的使用,線程的調度順序不影響任何結果,這個時候使用多線程,我們只需要關注系統(tǒng)功能的內(nèi)存,Cpu是不是夠用即可,反過來,線程不安全就意味著線程的調度順序會響應最終結果
- 多個線程同時操作一個全局變量是不安全的髓介,使用自旋鎖并不是絕對的安全(因為單寫多讀)。
- 在多個線程進行讀寫操作時筋现,仍然能夠保證數(shù)據(jù)的正確 唐础。使用互斥鎖可以實現(xiàn),但是消耗性能
- 所有更新UI的操作都在主線程上執(zhí)行
線程的狀態(tài)
新建狀態(tài):使用
new
關鍵字和Thread
類或其子類建立一個線程對象后矾飞,該線程對象就處于新建狀態(tài)一膨。它保持這個狀態(tài)直到程序start()
這個線程。就緒狀態(tài):當線程對象調用了
start()
方法之后洒沦,該線程就進入就緒狀態(tài)豹绪。就緒狀態(tài)的線程處于就緒隊列中,要等待JVM里線程調度器的調度申眼。阻塞(Blocked) : 對Running狀態(tài)的線程加同步鎖
synchronized
使其今如(Lock Blocked Pool)
同步鎖被釋放今如可運行狀態(tài),等待(Waiting) :線程可以主動調用
obj.wait
或者Thread.sleep
或者 join 今入,Waiting是等待另一個線程完成某一個操作,如join等待另一個完成執(zhí)行.waiting 和 Blocked狀態(tài), Blocked也是一種等待,等待的是monitor 但是waiting等待是notify()方法
每個對象都有的機制
- monitor :Java中 每個對象都有一個監(jiān)視器,來監(jiān)視并發(fā)代碼的重入,在非多線程編碼時改監(jiān)視器不發(fā)揮作用,反之如果在``synchronized`范圍內(nèi),監(jiān)視器發(fā)揮作用.
- wait/notify 必須存在于
synchronized
塊中, 并且這三個關鍵字針對的是同一個監(jiān)視器,意味著wait
之后,其他線程可以進入同步代碼塊 - synchronized 單獨使用 在多線程環(huán)境下瞒津,
synchronized
塊中的方法獲取了lock
實例的monitor
,如果實例相同豺型,那么只有一個線程能執(zhí)行該塊內(nèi)容
優(yōu)雅地停止線程
interrupt
阻塞中斷和非阻塞中斷interrupt()
方法并不會立即執(zhí)行中斷操作仲智,這個方法只會給線程設置一個為true的中斷標志。設置之后姻氨,則根據(jù)線程當前的狀態(tài)進行不同的后續(xù)操作钓辆。-
線程的當前狀態(tài)處于非阻塞狀態(tài),那么僅僅是線程的中斷標志被修改為true而已(2)如果線程的當前狀態(tài)處于阻塞狀態(tài)肴焊,那么在將中斷標志設置為true后前联,如果是 wait、sleep以及join 三個方法引起的阻塞娶眷,那么會將線程的中斷標志重新設置為false似嗤,并拋出一個
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); //調用中斷烁落,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); //非阻塞中斷 只是設置標記位true //非阻塞中斷 只是設置標記位true if(interruptStatus){ //如果中斷為true則退出 break; } } catch (InterruptedException e) { // 一個線程在運行狀態(tài)中乘粒,其中斷標志被設置為true之后,一旦線程調用了 // wait伤塌、join灯萍、sleep方法中的一種,立馬拋出一個InterruptedException每聪,且中斷標志被程序會自動清除旦棉,重新設置為false System.out.println("阻塞中斷"+Thread.currentThread().isInterrupted());//顯示false并拋異常 return;//不想返回還可繼續(xù)寫代碼 } } } } 子線程循環(huán)第1次false 子線程循環(huán)第2次false 子線程循環(huán)第3次false 阻塞中斷false
-
Stop()
停止- 由于不安全,已經(jīng)不使用了药薯,因為
stop
會解除由線程獲取的所有鎖定绑洛,當在一個線程對象上調用stop()
方法時,這個線程對象所運行的線程就會立即停止童本,假如一個線程正在執(zhí)行:synchronized void { x = 3; y = 4;}
由于方法是同步的真屯, 多個線程訪問時總能保證x,y被同時賦值,而如果一個線程正在執(zhí)行到x = 3;時巾陕,被調用了 stop()方法讨跟,即使在同步塊中,它也會馬上stop了鄙煤,這樣就產(chǎn)生了不完整的殘廢數(shù)據(jù)。
- 由于不安全,已經(jīng)不使用了药薯,因為
設置標記位停止
public class TestThread2_1 {
public static void main(String[] args) throws InterruptedException {
MyThreads my = new MyThreads();
new Thread(my, "線程A").start();
Thread.sleep(10000);
//設置標記位
//my.setFlag(false);
//stop方法
new Thread(my, "線程A").stop();
System.out.println("代碼結束");
}
}
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í)行茶袒,線程名稱為:" + Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
多線程控制類
ThreadLocal
類(關于這個類后續(xù)會推出關于這個類的專題)
用處: 保存線程的獨立變量, 對一個下線程類使用
ThreadLocal
維護變量時,ThreadLocal
為每個使用該變量的線程單獨提供了獨立的變量,所以每個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本,長用于用戶登錄控制,如記錄session信息-
常用方法
方法 作用 initValue() 副本創(chuàng)建的方法 get() 得到副本 set() 設置副本 -
實例
-
ThreadLocal
能實現(xiàn)為不同的線程保存變量的原理是梯刚,它內(nèi)部有個Entry,保存<線程名,變量值>,不同的線程對應著不同的value,就能加以區(qū)分了
public class ThreadLocalDemo { //銀行對象薪寓,有錢亡资,有存款和取款兩個操作 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); } } //轉賬對象,從銀行中取錢然后轉賬向叉,然后保存到賬戶 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類
-
Lock
類實際上是一個接口锥腻,我們在實例化的時候實際上是實例化實現(xiàn)了該接口的類
Lock lock = new ReentrantLock();
-
作用 :通過Lock對象lock,用
lock.lock
來加鎖 用lock.unlock
來釋放鎖母谎。在兩者中間放置需要同步處理的代碼瘦黑。- 可重入鎖(
ReentrantLock
): 線程請求它已經(jīng)擁有的鎖時不會阻塞,可以進入它已經(jīng)擁有的同步代碼塊奇唤,但是它擁有了多少次幸斥,就要解鎖多少次才能釋放鎖 - 讀寫鎖(
ReadWriteLock
) :可以同時讀,但是讀的時候不能寫咬扇,也不能同時寫甲葬,寫的時候不能讀
public class MyConditionService { private Lock lock = new ReentrantLock();//ReentrantLock是個重入鎖 public void testMethod(){ lock.lock();//加鎖 for (int i = 0 ;i < 5;i++){ System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1))); } lock.unlock();//解鎖 } }
- 可重入鎖(
-
其他作用
- 實現(xiàn)鎖的公平
- 獲取當前線程調用lock的次數(shù),也就是獲取當前線程鎖定的個數(shù)
- 獲取等待鎖的線程數(shù)
- 查詢指定的線程是否等待獲取此鎖定
- 查詢是否有線程等待獲取此鎖
- 查詢當前線程是否持有鎖定
- 判斷一個鎖是不是被線程持有
- 加鎖時如果中斷則不加鎖懈贺,進入異常處理
- 嘗試加鎖经窖,如果該鎖未被其他線程持有的情況下成功
Condition類
Condition是Java提供了來實現(xiàn)等待/通知的類坡垫,Condition類還提供比wait/notify更豐富的功能,Condition對象是由lock對象所創(chuàng)建的画侣。但是同一個鎖可以創(chuàng)建多個Condition的對象冰悠,即創(chuàng)建多個對象監(jiān)視器。這樣的好處就是可以指定喚醒線程棉钧。notify喚醒的線程是隨機喚醒一個屿脐。
condition對象通過
lock.newCondition()
來創(chuàng)建,用condition.await()
來實現(xiàn)讓線程等待宪卿,使得線程進入阻塞的诵。用condition.signal()
來實現(xiàn)喚醒線程。喚醒的線程是用同一個conditon
對象調用await()
方法而進入阻塞佑钾。并且和wait/notify
一樣西疤,await()
和signal()
也是在同步代碼區(qū)內(nèi)執(zhí)行。
對于等待/通知機制休溶,簡化而言代赁,就是等待一個條件,當條件不滿足時兽掰,就進入等待芭碍,等條件滿足時,就通知等待的線程開始執(zhí)行孽尽。為了實現(xiàn)這種功能窖壕,需要進行wait的代碼部分與需要進行通知的代碼部分必須放在同一個對象監(jiān)視器里面。執(zhí)行才能實現(xiàn)多個阻塞的線程同步執(zhí)行代碼杉女,等待與通知的線程也是同步進行瞻讽。對于wait/notify而言,對象監(jiān)視器與等待條件結合在一起 即synchronized
(對象)利用該對象去調用wait以及notify熏挎。但是對于``Condition類速勇,是對象監(jiān)視器與條件分開,Lock類來實現(xiàn)對象監(jiān)視器坎拐,
condition`對象來負責條件烦磁,去調用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的時間為 " + System.currentTimeMillis()); condition.await(); System.out.println("await結束的時間" + System.currentTimeMillis()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void signal(){ try{ lock.lock(); System.out.println("sign的時間為" + 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類提供了一個最長等待時間廉白,
awaitUntil(Date deadline)
在到達指定時間之后个初,線程會自動喚醒。但是無論是await
或者awaitUntil
猴蹂,當線程中斷時院溺,進行阻塞的線程會產(chǎn)生中斷異常。Java提供了一個awaitUninterruptibly
的方法磅轻,使即使線程中斷時珍逸,進行阻塞的線程也不會產(chǎn)生中斷異常逐虚。
- 其他作用:和wait類提供了一個最長等待時間廉白,