(Java中線程池,你真的會(huì)用嗎掷豺?)[https://developer.aliyun.com/article/756612?scm=20140722.184.2.173]
?進(jìn)程:一個(gè)啟動(dòng)的應(yīng)用程序,是線程的載體.進(jìn)程也是程序的一次執(zhí)行過程捞烟,是系統(tǒng)運(yùn)行程序的基本單位;系統(tǒng)運(yùn)行一個(gè)程序即是一個(gè)進(jìn)程從創(chuàng)建当船、運(yùn)行到消亡的過程
?線程是系統(tǒng)中最小的執(zhí)行單元.JVM搶占式調(diào)度
?啟動(dòng)應(yīng)用程序啟動(dòng)進(jìn)程,進(jìn)程默認(rèn)情況下會(huì)啟動(dòng)主線程
進(jìn)程與線程的區(qū)別
進(jìn)程:有獨(dú)立的內(nèi)存空間题画,進(jìn)程中的數(shù)據(jù)存放空間(堆空間和棧空間)是獨(dú)立的德频,至少有一個(gè)線程苍息。
線程:堆空間是共享的,椧贾茫空間是獨(dú)立的竞思,線程消耗的資源比進(jìn)程小的多。
一.多線程的創(chuàng)建方式:
1. 繼承(extends) Thread
- 定義Thread類的子類蒸绩,并重寫該類的run()方法衙四,該run()方法的方法體就代表了線程需要完成的任務(wù),因此把run()方法稱為線程執(zhí)行體。
- 創(chuàng)建Thread子類的實(shí)例患亿,即創(chuàng)建了線程對象
- 調(diào)用線程對象的start()方法來啟動(dòng)該線程
缺點(diǎn):
直接將任務(wù)的實(shí)現(xiàn)寫到了線程類當(dāng)中传蹈,耦合度太高
//自定義中斷標(biāo)記,實(shí)現(xiàn)指定時(shí)間中斷
class MyThread extends Thread {
private boolean needInterrupt = false;
public void setNeedInterrupt(boolean needInterrupt) {
this.needInterrupt = needInterrupt;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (needInterrupt) return;
System.out.println("打印" + i);
try {
sleep(1000L);
} catch (InterruptedException e) {
System.out.println("報(bào)錯(cuò)" + e.getMessage());
e.printStackTrace();
}
}
}}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("ccc");
// thread.setPriority(10); 設(shè)置線程優(yōu)先級
thread.start();
try {
thread.sleep(3000L);
thread.interrupt();
thread.setNeedInterrupt(true);
} catch (InterruptedException e) {
e.printStackTrace();
}}}
2.實(shí)現(xiàn)(implements) Runnable
- 定義Runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法步藕,該run()方法的方法體同樣是該線程的線程執(zhí)行體惦界。
- 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread的target來創(chuàng)建Thread對象咙冗,該Thread對象才是真正的線程對象沾歪。
- 調(diào)用線程對象的start()方法來啟動(dòng)線程。
Runnable
不返回結(jié)果雾消,也不能拋出被檢查的異常
//多線程賣100張票
public class MyRunnable implements Runnable {
private int Ticket = 100;
@Override
public void run() {
while (true) {
synchronized (MyRunnable.class) { //用this也可
if (Ticket > 0) {
System.out.println("賣出第" + (101 - Ticket) + "張票");
Ticket--;
} else return;
}
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.setName("窗口1");
thread1.setName("窗口2");
thread1.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
如果一個(gè)類繼承Thread灾搏,則不適合資源共享。但是如果實(shí)現(xiàn)了Runnable接口的話立润,則很容易實(shí)現(xiàn)資源共享
實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
- 適合多個(gè)相同的程序代碼的線程去共享同一個(gè)資源狂窑。
- 可以避免java中的單繼承的局限性。
- 增加程序的健壯性桑腮,實(shí)現(xiàn)解耦操作泉哈,代碼可以被多個(gè)線程共享,代碼和線程獨(dú)立。
- 線程池只能放入實(shí)現(xiàn)Runable或Callable類線程丛晦,不能直接放入繼承Thread的類
2.1 匿名內(nèi)部類創(chuàng)建
使用線程的匿名內(nèi)部類方式奕纫,可以方便的實(shí)現(xiàn)每個(gè)線程執(zhí)行不同的線程任務(wù)操作。
使用匿名內(nèi)部類的方式實(shí)現(xiàn)Runnable接口烫沙,重寫Runnable接口中的run方法:
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("美美" + i);
}
}
},"hello");
new Thread(thread).start();
System.out.println(thread.getName());
Thread.currentThread().setName("美眉"); //設(shè)置main線程名字
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+":妹"+i);
}
}
}
3.Callable
有返回值(可選),可以拋出異常
import java.util.concurrent.Callable;
public class MyCallAble implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
return "callable返回了數(shù)據(jù)";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main2 {
public static void main(String[] args) {
MyCallAble callAble = new MyCallAble();
ExecutorService service = Executors.newFixedThreadPool(5);
Future<String> submit = service.submit(callAble);
try {
System.out.println(submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
二匹层、線程安全
線程安全問題都是由全局變量及靜態(tài)變量引起的。
若每個(gè)線程中對全局變量斧吐、靜態(tài)變量只有讀操作又固,而無寫操作,一般來說煤率,這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫操作乏冀,一般都需要考慮線程同步蝶糯,否則的話就可能影響線程安全
1.同步代碼塊 synchronized
部分代碼需要加鎖
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖:
- 鎖對象 可以是任意類型。
- 多個(gè)線程對象 要使用同一把鎖辆沦。(需要保證唯一)
2.同步方法
修飾符 synchronized 返回值類型 方法名(){
可能會(huì)產(chǎn)生線程安全問題的代碼
}
同步鎖:
對于非static方法,同步鎖就是this昼捍。
對于static方法,我們使用當(dāng)前方法所在類的字節(jié)碼對象(類名.class)
3.鎖機(jī)制(同步鎖/lock鎖)
Lock lock = new ReentrantLock();
lock.lock();
采用Lock,必須主動(dòng)去釋放鎖肢扯,并且在發(fā)生異常時(shí)妒茬,不會(huì)自動(dòng)釋放鎖。因此一般來說蔚晨,
使用Lock必須在try{}catch{}塊中進(jìn)行乍钻,并且將釋放鎖的操作放在finally塊中進(jìn)行,以保證鎖一定被被釋放铭腕,防止死鎖的發(fā)生
Lock lock = ...;
lock.lock();
try{
//處理任務(wù)
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
三.線程狀態(tài)
NEW(新建)
線程剛被創(chuàng)建银择,但是并未啟動(dòng)。還沒調(diào)用start方法累舷。
Runnable (可運(yùn)行)
線程可以在java虛擬機(jī)中運(yùn)行的狀態(tài)浩考,可能正在運(yùn)行自己代碼,也可能沒有被盈,這取決于操
作系統(tǒng)處理器析孽。
Blocked(鎖阻塞)
當(dāng)一個(gè)線程試圖獲取一個(gè)對象鎖,而該對象鎖被其他的線程持有只怎,則該線程進(jìn)入Blocked狀
態(tài)袜瞬;當(dāng)該線程持有鎖時(shí),該線程將變成Runnable狀態(tài)尝盼。
Waiting(無限等待)
一個(gè)線程在等待另一個(gè)線程執(zhí)行一個(gè)(喚醒)動(dòng)作時(shí)吞滞,該線程進(jìn)入Waiting狀態(tài)。進(jìn)入這個(gè)
狀態(tài)后是不能自動(dòng)喚醒的,必須等待另一個(gè)線程調(diào)用notify或者notifyAll方法才能夠喚醒裁赠。
TimedWaiting(計(jì)時(shí)等待)
同waiting狀態(tài)殿漠,有幾個(gè)方法有超時(shí)參數(shù),調(diào)用他們將進(jìn)入Timed Waiting狀態(tài)佩捞。這一狀態(tài)
將一直保持到超時(shí)期滿或者接收到喚醒通知绞幌。帶有超時(shí)參數(shù)的常用方法有Thread.sleep 、
Object.wait一忱。
Teminated(被終止)
因?yàn)閞un方法正常退出而死亡莲蜘,或者因?yàn)闆]有捕獲的異常終止了run方法而死亡
四.線程通信(等待喚醒機(jī)制)
4.1. 2個(gè)線程間使用flag變量實(shí)現(xiàn)交替執(zhí)行
//實(shí)現(xiàn)2個(gè)線程間交替執(zhí)行
public class Demo {
int flag = 1;
public synchronized void method1() {
if (flag != 1) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("學(xué)");
System.out.print("習(xí)");
System.out.print("了");
System.out.print("嗎");
System.out.println();
this.flag = 2;
this.notify();
}
public synchronized void method2() {
if (flag != 2) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("s");
System.out.print("t");
System.out.print("u");
System.out.print("d");
System.out.print("y");
System.out.println();
this.flag = 1;
this.notify();
}
}
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
demo.method1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
demo.method2();
}
}
}).start();
}
}
4.2 替換if為 whie可實(shí)現(xiàn)3個(gè)線程間有效通信(交替執(zhí)行)
4.3 生產(chǎn)者消費(fèi)者案例
//生產(chǎn)者
public class Producter extends Thread {
BaoZi bz;
@Override
public void run() {
int num = 0;
while (true) {
synchronized (bz) {
if (bz.flag == true) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生產(chǎn)者喚醒,開始生產(chǎn)");
if (num % 2 == 0) {
bz.pier = "面皮";
bz.xianer = "豬肉大蔥";
} else {
bz.xianer = "黑芝麻";
bz.pier = "米皮";
}
System.out.println("包子皮是:" + bz.pier + ";包子餡是:" + bz.xianer);
num++;
bz.flag = true;
bz.notify();
}
}
}
}
//消費(fèi)者
public class Eater extends Thread {
BaoZi bz;
@Override
public void run() {
while (true) {
synchronized (bz) {
if (bz.flag == false) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃貨開始吃包子,皮是:" + bz.pier + ";餡是:" + bz.xianer);
bz.flag = false;
bz.notify();
System.out.println();
}
}
}
}
public class BaoZi {
boolean flag = false;
String pier;
String xianer;
}
public class Main1 {
public static void main(String[] args) {
BaoZi baoZi = new BaoZi();
Producter pt = new Producter();
pt.bz = baoZi;
pt.start();
Eater eater = new Eater();
eater.bz = baoZi;
eater.start();
}
}
五.線程池 ExecutorService(interface)
5.1 合理使用的好處:
- 降低資源消耗。
- 提高響應(yīng)速度帘营。
- 提高線程的可管理性
官方建議使用Executors工程類來創(chuàng)建線程池對象
阿里巴巴開發(fā)手冊并發(fā)編程:線程池不允許使用 Executors 去創(chuàng)建票渠,而是通過 ThreadPoolExecutor 的方式
Executors 創(chuàng)建線程池的方式
根據(jù)返回的對象類型創(chuàng)建線程池可以分為三類:
創(chuàng)建返回 ThreadPoolExecutor 對象
創(chuàng)建返回 ScheduleThreadPoolExecutor 對象
創(chuàng)建返回 ForkJoinPool 對象
5.2 使用步驟
創(chuàng)建線程池對象。
創(chuàng)建Runnable接口子類對象芬迄。(task)
-
提交Runnable接口子類對象问顷。(take task)
execute 無返回值
submit 有返回值
-
關(guān)閉線程池(一般不做) shutdown
public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i+" "+Thread.currentThread().getName()); try { Thread.sleep(10L); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Main { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1); //Runtime.getRuntime().availableProcessors()獲取當(dāng)前cpu核心數(shù) MyRunnable runnable = new MyRunnable(); service.execute(runnable); //execute 無返回值 Future<?> submit = service.submit(runnable); //submit 有返回值 } }
六.線程 join
?將線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行
七.守護(hù)線程 setDaemon
?守護(hù)線程是指在程序運(yùn)行的時(shí)候在后臺提供一種通用的服務(wù)的線程(垃圾回收線程)
?主線程執(zhí)行結(jié)束,守護(hù)線程就結(jié)束
守護(hù)線程設(shè)置在線程啟動(dòng)之前.參數(shù)為true表示是daemon thread.
thread2.setDaemon(true);
thread2.start();