一、基本概念
1.程序:指令集 靜態(tài)概念
2.進(jìn)程:操作系統(tǒng)調(diào)度程序,是一個(gè)動(dòng)態(tài)的概念。是程序的一次動(dòng)態(tài)執(zhí)行的過(guò)程,占用特定的地址空間。每個(gè)進(jìn)程都是獨(dú)立的,由3部分組成
cpu、data、code
。缺點(diǎn):內(nèi)存的浪費(fèi),cpu
負(fù)擔(dān)重。3.線(xiàn)程:在進(jìn)程內(nèi)多條執(zhí)行路徑。線(xiàn)程是進(jìn)程中一個(gè)單一的連續(xù)控制流程(執(zhí)行路徑)。線(xiàn)程又被稱(chēng)為輕量級(jí)進(jìn)程,一個(gè)進(jìn)程可擁有多個(gè)并行的線(xiàn)程,一個(gè)進(jìn)程中的線(xiàn)程共享相同的內(nèi)存單元/內(nèi)存地址空間-->可以訪問(wèn)相同的變量和對(duì)象,而且它們從同一堆中分配對(duì)象-->通信、數(shù)據(jù)交換、同步操作。由于線(xiàn)程間的通信是在同一地址空間上進(jìn)行的,所以不需要額外的通信機(jī)制,這就使得通信更簡(jiǎn)便而且信息傳遞的速度也更快。
4.比較
區(qū)別 | 進(jìn)程 | 線(xiàn)程 |
---|---|---|
根本區(qū)別 | 作為資源分配的單位 | 調(diào)度和執(zhí)行的單位 |
開(kāi)銷(xiāo) | 每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文),進(jìn)程間的切換會(huì)有較大的開(kāi)銷(xiāo) | 線(xiàn)程可以看成是輕量級(jí)的進(jìn)程,同一類(lèi)線(xiàn)程共享代碼和數(shù)據(jù)空間,每個(gè)線(xiàn)程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器PC線(xiàn)程切換的開(kāi)銷(xiāo)小 |
所處環(huán)境 | 在操作系統(tǒng)中能同時(shí)運(yùn)行多個(gè)任務(wù)(程序) | 在同一應(yīng)用程序中有多個(gè)順序流同時(shí)執(zhí)行 |
分配內(nèi)存 | 系統(tǒng)在運(yùn)行的時(shí)候會(huì)為每個(gè)進(jìn)程分配不同的內(nèi)存區(qū)域 | 除了cpu之外,不會(huì)為線(xiàn)程分配內(nèi)存(線(xiàn)程所使用的資源是它所屬的進(jìn)程的資源)線(xiàn)程組只能共享資源 |
包含關(guān)系 | 沒(méi)有線(xiàn)程的進(jìn)程是可以被看作單線(xiàn)程的,如果一個(gè)進(jìn)程內(nèi)擁有多個(gè)線(xiàn)程,則執(zhí)行過(guò)程不是一條線(xiàn)程的,而是多條線(xiàn)(線(xiàn)程)共同完成的 | 線(xiàn)程是進(jìn)程的一部分,所以線(xiàn)程有的時(shí)候被稱(chēng)為是輕量級(jí)進(jìn)程或輕權(quán)進(jìn)程 |
二、相關(guān)類(lèi)的使用
2.1 使用Thread類(lèi)實(shí)現(xiàn)多線(xiàn)程
這里我們給出一個(gè)簡(jiǎn)單的例子:
Rabbit_01.java
package cn.itcast.day169.thread;
/*
* 模擬龜兔賽跑
* 1、創(chuàng)建多線(xiàn)程,繼承Thread類(lèi)+重寫(xiě)run方法(其中的代碼都是線(xiàn)程體)
* 2、使用線(xiàn)程:創(chuàng)建子類(lèi)對(duì)象+對(duì)象.start(),調(diào)用start方法表示線(xiàn)程的啟動(dòng)
* */
//兔子
public class Rabbit_01 extends Thread{
public void run() {
//線(xiàn)程體
for(int i = 0; i < 100; i++){
System.out.println("兔子跑了: " + i + "步");
}
}
}
//烏龜
class Tortoise extends Thread{
public void run() {
//線(xiàn)程體
for(int i = 0; i < 100; i++){
System.out.println("烏龜跑了: " + i + "步");
}
}
}
RabbitApp_01.java
package cn.itcast.day169.thread;
//我們會(huì)將此類(lèi)加到一個(gè)線(xiàn)程組中
public class RabbitApp_01 {
public static void main(String[] args) {
//創(chuàng)建子類(lèi)對(duì)象
Rabbit_01 rab = new Rabbit_01();
Tortoise tor = new Tortoise();
//調(diào)用start方法
rab.start();//不要調(diào)用run方法,調(diào)用run方法只是調(diào)用一個(gè)普通方法,沒(méi)有開(kāi)啟一個(gè)線(xiàn)程
tor.start();//此時(shí)這里就有兩條線(xiàn)程了
for(int i = 0; i < 1000; i++){
System.out.println("main " + i );
}
}
}
說(shuō)明:這里可以看到我們通過(guò)繼承Thread
類(lèi)實(shí)現(xiàn)了多線(xiàn)程,但是很明顯有個(gè)缺點(diǎn),就是如果一個(gè)類(lèi)本身就已經(jīng)繼承了一個(gè)父類(lèi),那么此類(lèi)就不能再繼承Thread
類(lèi)來(lái)實(shí)現(xiàn)多線(xiàn)程了。
2.2 使用Runnable實(shí)現(xiàn)多線(xiàn)程
為了解決上面的問(wèn)題,我們可以實(shí)現(xiàn)Runnable
接口。優(yōu)點(diǎn):可以同時(shí)實(shí)現(xiàn)繼承,實(shí)現(xiàn)Runnable
接口方式要通用一些。
- 1)避免單繼承
- 2)方便共享資源 , 同一份資源, 多個(gè)代理訪問(wèn)。
Runnable
使用了靜態(tài)代理的設(shè)計(jì)模(繼承相同的接口),首先我們看什么是靜態(tài)設(shè)計(jì)模式:
StaticProxy.java
package cn.itcast.day169.thread;
/*靜態(tài)代理
* 1、真實(shí)角色
* 2、代理角色:要持有真實(shí)角色的引用
* 3、二者實(shí)現(xiàn)相同的接口
* */
public class StaticProxy {
public static void main(String[] args) {
You you = new You();//創(chuàng)建真實(shí)角色
//創(chuàng)建代理角色+ 持有真實(shí)角色的引用
WeddingCompany company = new WeddingCompany(you);
//執(zhí)行任務(wù)
company.marry();
}
}
//接口
interface Marry{
public abstract void marry();
}
//真實(shí)角色
class You implements Marry{
public void marry() {
System.out.println("you and girlfriend marry");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you ;
public WeddingCompany() {}
//這里我們通過(guò)構(gòu)造方法傳遞真實(shí)引用,當(dāng)然我們也可以使用setter方法來(lái)傳遞
public WeddingCompany(Marry you) {
this.you = you;
}
private void befor(){
System.out.println("布置新房");
}
public void marry() {
//在這里我們可以添加一些真實(shí)對(duì)象中沒(méi)有的方法,完成一些額外的工作
befor();
you.marry();
after();
}
private void after(){
System.out.println("鬧婚房");
}
}
說(shuō)明:靜態(tài)代理本質(zhì)上就是一個(gè)類(lèi)替代執(zhí)行另一個(gè)類(lèi)的相關(guān)方法,在執(zhí)行此方法的時(shí)候還會(huì)附加執(zhí)行一些操作,而不是直接去執(zhí)行實(shí)際類(lèi)的方法,相當(dāng)于一種擴(kuò)展。在實(shí)現(xiàn)靜態(tài)代理的時(shí)候需要注意的是,代理類(lèi)和被代理類(lèi)需要實(shí)現(xiàn)相同的接口,而執(zhí)行的方法都是代理類(lèi)中的方法,同時(shí)代理類(lèi)中的方法需要完成被代理類(lèi)中需要完成的功能。例子中代理類(lèi)WeddingCompany
和被代理類(lèi)You
都繼承的接口是Marry
,本來(lái)是You
結(jié)婚,但是我們將這項(xiàng)工作交給代理類(lèi),不僅完成結(jié)婚的工作,還處理了一些其他事情。
下面我們看如何使用Runnable
實(shí)現(xiàn)多線(xiàn)程:
Programer.java
package cn.itcast.day169.thread;
//使用Runnable創(chuàng)建線(xiàn)程
/*1、類(lèi)實(shí)現(xiàn)Runable接口,重寫(xiě)run方法-->真實(shí)角色類(lèi)
* */
public class Programer implements Runnable{
public void run() {
for(int i = 0; i < 1000; i++){
System.out.println("一邊做a, 一邊做b");
}
}
}
ProgramerApp.java
package cn.itcast.day169.thread;
//使用Runnable創(chuàng)建線(xiàn)程
/*1、類(lèi)實(shí)現(xiàn)Runable接口,重寫(xiě)run方法-->真實(shí)角色類(lèi)
* 2.啟動(dòng)多線(xiàn)程,使用靜態(tài)代理
* 1)創(chuàng)建真實(shí)角色
* 2)創(chuàng)建代理角色
* 3)調(diào)用start方法啟動(dòng)線(xiàn)程
* */
public class ProgramerApp {
public static void main(String[] args) {
//1)創(chuàng)建真實(shí)角色
Programer programer = new Programer();
//2)創(chuàng)建代理角色
Thread proxy = new Thread(programer);
//3)調(diào)用start方法啟動(dòng)線(xiàn)程
proxy.start();
for(int i = 0; i < 1000; i++){
System.out.println("一邊聊天");
}
}
}
說(shuō)明:這里我們看到實(shí)現(xiàn)實(shí)例化被代理類(lèi)Programer
,然后使用此類(lèi)創(chuàng)建Thread
代理類(lèi)。這樣便實(shí)現(xiàn)了多線(xiàn)程。這里我們推薦使用這種方法。此種方式避免單繼承的局限性,同時(shí)方便共享資源。
2.3 通過(guò)Callable接口實(shí)現(xiàn)多線(xiàn)程
在上面的方式中我們可以看到使用
Runnable
接口時(shí),run
方法是不能有返回值的,但是有時(shí)候我們不可避免的希望得到返回值,此時(shí)我們就可以使用Callable
這個(gè)接口。Callable
是類(lèi)似于Runnable
的接口,實(shí)現(xiàn)Callable
接口的類(lèi)和實(shí)現(xiàn)Runnable
的類(lèi)都是可被其它線(xiàn)程執(zhí)行的任務(wù)。Callable
和Runnable
有幾點(diǎn)不同:
1)Callable
規(guī)定的方法是call()
,而Runnable
規(guī)定的方法是run
2)call
方法可拋出異常,而run
方法是不能拋出異常的
3)Callable
的任務(wù)執(zhí)行后返回值,運(yùn)行Callable
任務(wù)可拿到一個(gè)Future
對(duì)象,而Runnable
的任務(wù)是不能返回值的。Futrue
表示異步計(jì)算的結(jié)果。它提供了檢查計(jì)算是否完成的方法啊,以等待計(jì)算的完成,并檢索計(jì)算的結(jié)果。通過(guò)Future
對(duì)象可以了解任務(wù)執(zhí)行的情況,可取任務(wù)的執(zhí)行,還可以獲取任務(wù)的執(zhí)行的結(jié)果。
缺點(diǎn):繁瑣使用方式
1)創(chuàng)建Callable
實(shí)現(xiàn)類(lèi)+重寫(xiě)call
方法
2)借助執(zhí)行調(diào)度服務(wù)ExecutorService
獲取Futrue
對(duì)象
ExecutorService ser = Executors.newFixedThreadPool(2);
Futrure result = ser.submit(實(shí)現(xiàn)類(lèi)對(duì)象);
3)、獲取值result.get()
4)停止服務(wù)ser.shutdownNow()
Callable_01.java
package cn.itcast.day169.thread;
import java.util.concurrent.*;
/*使用Callable創(chuàng)建線(xiàn)程 */
public class Callable_01 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//創(chuàng)建兩個(gè)線(xiàn)程
ExecutorService service = Executors.newFixedThreadPool(2);
Race tortoise = new Race("千年王八", 1000);
Race rabbit = new Race("兔子", 500);
//獲取值
Future<Integer> result1 = service.submit(tortoise);
Future<Integer> result2 = service.submit(rabbit);
Thread.sleep(2000);//2秒
tortoise.setFlag(false);//停止線(xiàn)程體循環(huán)
rabbit.setFlag(false);
int num1 = result1.get();
int num2 = result2.get();
System.out.println("烏龜跑了 " + num1 + "步");
System.out.println("兔子跑了 " + num2 + "步");
//關(guān)閉線(xiàn)程
service.shutdownNow();
}
}
class Race implements Callable<Integer>{
private String name ;//名稱(chēng)
private long time ;//延時(shí)時(shí)間,用來(lái)表示速度
private boolean flag = true;
private int step = 0;//步
public Race(){}
public Race(String name ){
this.name = name;
}
public Race(String name, long time) {
this.name = name;
this.time = time;
}
//重寫(xiě)call方法
public Integer call() throws Exception {
while(flag){
Thread.sleep(time);
step++;
}
return step;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
}
說(shuō)明:這里的使用方式和之前的稍微有點(diǎn)不同。
三、線(xiàn)程狀態(tài)
3.1 基本概念
線(xiàn)程被創(chuàng)建之后我們調(diào)用
start
方法,此時(shí)線(xiàn)程進(jìn)入就緒狀態(tài)(可運(yùn)行狀態(tài)),當(dāng)cpu
調(diào)度的時(shí)候進(jìn)入運(yùn)行狀態(tài),當(dāng)線(xiàn)程執(zhí)行完后此線(xiàn)程就停止了,即終止?fàn)顟B(tài)。但是如果線(xiàn)程在還沒(méi)有執(zhí)行完的時(shí)候被停止,那么就進(jìn)入阻塞狀態(tài),如果阻塞狀態(tài)解除之后線(xiàn)程又進(jìn)入就緒狀態(tài),在之后cpu
調(diào)度則還會(huì)接著進(jìn)入運(yùn)行狀態(tài)進(jìn)行執(zhí)行。這里注意:死亡狀態(tài)是線(xiàn)程聲明周期中的最后一個(gè)階段,線(xiàn)程死亡的原因有兩個(gè)。一個(gè)是正常執(zhí)行完,一個(gè)是線(xiàn)程被強(qiáng)制性的終止工作,如果通過(guò)執(zhí)行
stop
或destroy
方法來(lái)終止一個(gè)線(xiàn)程(不推薦使用這兩個(gè)方法),前者會(huì)產(chǎn)生異常,后者是強(qiáng)制終止,不會(huì)釋放鎖。-
死亡狀態(tài)如何停止線(xiàn)程
在之前的例子中,其實(shí)我們已經(jīng)看到,我們可以通過(guò)一個(gè)標(biāo)志來(lái)停止線(xiàn)程。
1、自然終止
2、外部干涉- 1)線(xiàn)程類(lèi)中定義線(xiàn)程體使用的標(biāo)志
- 2)線(xiàn)程體使用該標(biāo)志
- 3)提供對(duì)外的方法可以改變?cè)摌?biāo)志
- 4)外部根據(jù)條件調(diào)用該方法即可
使用標(biāo)志位來(lái)停止線(xiàn)程:
package cn.itcast.day174.thread01;
public class Demo01 {
public static void main(String[] args) {
Study s = new Study();
new Thread(s).start();
//外部干涉
for(int i = 0; i < 100; i++){
if(i == 50){//注意:這里停下來(lái)的時(shí)間點(diǎn)不是很準(zhǔn)確,由cpu決定
s.stop();
}
System.out.println("main-->" + i);
}
}
}
class Study implements Runnable{
private boolean flag = true;
public void run() {
while(flag){
System.out.println("study thread...");
}
}
//對(duì)外提供該表標(biāo)志的方法
public void stop(){
this.flag = false;
}
}
- 阻塞
- 1、
join
:合并線(xiàn)程,將多條線(xiàn)程合并為一個(gè) - 2、
yield
:暫停當(dāng)前線(xiàn)程(暫停自己),執(zhí)行其他線(xiàn)程 - 3、
sleep
:暫停線(xiàn)程,休眠的時(shí)候不會(huì)釋放鎖。相關(guān)應(yīng)用:
1)與時(shí)間相關(guān):數(shù)數(shù)和倒計(jì)時(shí)
2)模擬網(wǎng)絡(luò)延時(shí)
- 1、
合并線(xiàn)程
package cn.itcast.day174.thread01;
/*合并線(xiàn)程 */
public class JoinDemo01 extends Thread{
public static void main(String[] args) throws InterruptedException {
JoinDemo01 demo = new JoinDemo01();
Thread t = new Thread(demo);//新生狀態(tài)
t.start();//就緒狀態(tài)
//cpu調(diào)度就進(jìn)入運(yùn)行狀態(tài)
for(int i = 0; i < 100; i++){
if(i == 50){
//此時(shí)main方法阻塞了,必須讓線(xiàn)程t運(yùn)行完,就是剛開(kāi)始誰(shuí)先執(zhí)行都是隨機(jī)的,
//但是當(dāng)i的值為50后,便是t線(xiàn)程執(zhí)行完后main線(xiàn)程才能再次執(zhí)行
t.join();
}
System.out.println("main..." + i);
}
}
public void run(){
for(int i = 0; i < 100; i++){
System.out.println("join..." + i);
}
}
}
說(shuō)明:這里其實(shí)是有兩個(gè)線(xiàn)程,一個(gè)是線(xiàn)程t
,一個(gè)是main
。開(kāi)始時(shí)是并發(fā)執(zhí)行,但是到i
的值為50的時(shí)候便需要t
線(xiàn)程運(yùn)行完成之后main
線(xiàn)程才能開(kāi)始執(zhí)行。
暫停當(dāng)前線(xiàn)程
package cn.itcast.day174.thread01;
/*暫停執(zhí)行當(dāng)前的線(xiàn)程(暫停自己),并執(zhí)行其他線(xiàn)程*/
public class YieldDemo01 extends Thread{
public static void main(String[] args) throws InterruptedException {
YieldDemo01 demo = new YieldDemo01();
Thread t = new Thread(demo);//新生狀態(tài)
t.start();//就緒狀態(tài)
//cpu調(diào)度就進(jìn)入運(yùn)行狀態(tài)
for(int i = 0; i < 100; i++){
if(i % 20 == 0){
//注意:這里的暫停不是很?chē)?yán)格
Thread.yield();//暫停自己,注意:寫(xiě)在誰(shuí)的線(xiàn)程體內(nèi)就暫停誰(shuí)(這里暫停main),效果不是很明顯
}
System.out.println("main..." + i);
}
}
public void run(){
for(int i = 0; i < 100; i++){
System.out.println("yield..." + i);
}
}
}
說(shuō)明:這里一定要注意到底是暫停哪個(gè)線(xiàn)程。
數(shù)數(shù)和倒計(jì)時(shí)
package cn.itcast.day174.thread01;
import java.util.Date;
import java.text.SimpleDateFormat;
//倒計(jì)時(shí),倒數(shù)十個(gè)數(shù),一秒內(nèi)打印一個(gè)
public class SleepDemo01 {
public static void main(String[] args) throws InterruptedException {
test2();
}
public static void test1() throws InterruptedException {
int num = 10;
while (true) {
System.out.println(num--);
Thread.sleep(1000);// 暫停
if (num <= 0) {
break;
}
}
}
// 倒計(jì)時(shí)
public static void test2() throws InterruptedException {
Date endTime = new Date(System.currentTimeMillis() + 10 * 1000);
long end = endTime.getTime();
while (true) {
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
endTime = new Date(endTime.getTime() - 1000);
Thread.sleep(1000);
if (end - 10000 > endTime.getTime()) {
break;
}
}
}
}
模擬網(wǎng)絡(luò)延時(shí)
package cn.itcast.day174.thread01;
//模擬網(wǎng)絡(luò)延時(shí),可能出現(xiàn)并發(fā)問(wèn)題
public class SleepDemo02 {
public static void main(String[] args) {
Web12306 web = new Web12306();//真實(shí)角色
//代理對(duì)象
Thread t1 = new Thread(web, "黃牛1");//第二個(gè)參數(shù)是當(dāng)前線(xiàn)程的名字
Thread t2 = new Thread(web, "黃牛2");
Thread t3 = new Thread(web, "黃牛3");
//啟動(dòng)線(xiàn)程
t1.start();
t2.start();
t3.start();
}
}
class Web12306 implements Runnable{
private int num = 50;
public void run() {
while(true){
if(num <= 0){
break;
}
try {
Thread.sleep(500);//500ms的延時(shí)
//加入延時(shí)之后可能會(huì)造成資源沖突的問(wèn)題,這就是并發(fā)問(wèn)題
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到了第" + num-- + "張票");
}
}
}
- 線(xiàn)程基本信息
方法 | 功能 |
---|---|
isAlive() |
判斷線(xiàn)程是否還“活”著,即線(xiàn)程是否還未終止 |
getPriority() |
獲得線(xiàn)程的優(yōu)先級(jí)數(shù)值 |
setPriority() |
設(shè)置線(xiàn)程的優(yōu)先級(jí)數(shù)值 |
setName() |
給線(xiàn)程一個(gè)名字 |
getName() |
取得線(xiàn)程的名字 |
currentThread() |
取得當(dāng)前正在運(yùn)行的線(xiàn)程對(duì)象,也就是取得自己本身 |
下面給出兩個(gè)例子:
MyThread.java
package cn.itcast.day174.thread01;
public class MyThread implements Runnable {
private boolean flag = true;
private int num = 0;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName() + num++);
}
}
public void stop() {
this.flag = false;
}
}
InfoDemo01.java
package cn.itcast.day174.thread01;
//currentThread()靜態(tài)方法
public class InfoDemo01 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
Thread proxy = new Thread(t1, "t1線(xiàn)程");//第二個(gè)參數(shù)是線(xiàn)程的名稱(chēng)
//我們也可以這樣設(shè)置線(xiàn)程名稱(chēng)
proxy.setName("狗蛋");
System.out.println(proxy.getName());
System.out.println(Thread.currentThread().getName());//獲取名稱(chēng)
proxy.start();
System.out.println("線(xiàn)程啟動(dòng)后的狀態(tài): " + proxy.isAlive());//查看線(xiàn)程是否“活著”
Thread.sleep(200);
t1.stop();
Thread.sleep(200);
System.out.println("線(xiàn)程停止后的狀態(tài): " + proxy.isAlive());
}
}
InfoDemo02.java
package cn.itcast.day174.thread01;
//優(yōu)先級(jí)
//MAX_PRIORITY 10最大
//MIN_PRIORITY 1最小
//NORM_PRIORITY 5默認(rèn)
//優(yōu)先級(jí)不代表絕對(duì)的優(yōu)先,沒(méi)有先后順序,代表的是概率
public class InfoDemo02 {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
Thread p1 = new Thread(t1, "t1線(xiàn)程");
MyThread t2 = new MyThread();
Thread p2 = new Thread(t2, "t2線(xiàn)程");
p1.setPriority(Thread.MIN_PRIORITY);//設(shè)置優(yōu)先級(jí)
p2.setPriority(Thread.MAX_PRIORITY);//設(shè)置優(yōu)先級(jí)
p1.start();
p2.start();
Thread.sleep(100);
t1.stop();
t2.stop();
}
}
說(shuō)明:這兩個(gè)例子都是用來(lái)取得線(xiàn)程的一些信息。