1.進(jìn)程與線程
一個(gè)程序中至少有一個(gè)進(jìn)程伤溉,一個(gè)進(jìn)程中至少會(huì)有一個(gè)線程较解。一個(gè)進(jìn)程可以包含多個(gè)線程撩嚼。
看到一個(gè)蠻容易理解的比方:
cpu可以看做一個(gè)工廠捆憎,而進(jìn)程可以看做是一個(gè)車(chē)間按咒,每次這個(gè)工廠只能有一個(gè)車(chē)間工作迟隅,一旦有一個(gè)車(chē)間工作其他車(chē)間就需要停止工作。也就是說(shuō)在任何時(shí)候励七,cpu都只運(yùn)行一個(gè)進(jìn)程智袭。線程就是車(chē)間里的工人,一個(gè)車(chē)間可以有多個(gè)工人同時(shí)工作掠抬,他們可以協(xié)調(diào)共同完成一個(gè)任務(wù)吼野。而車(chē)間內(nèi)的房間,工人們是共享的,車(chē)間內(nèi)的工人們都可以進(jìn)出两波。這也就意味著一個(gè)進(jìn)程的內(nèi)存空間瞳步,線程是共享的闷哆。車(chē)間內(nèi)的房間大小不等,有的房間只能容下一個(gè)人单起,其他人想使用這個(gè)房間的話抱怔,只有這個(gè)小房間內(nèi)人出去后才可以。這代表一個(gè)線程使用某些共享內(nèi)存時(shí)嘀倒,其他線程必須等它結(jié)束屈留,才能使用這一塊內(nèi)存。
以上的理解是看到這篇博客進(jìn)程與線程的一個(gè)簡(jiǎn)單解釋寫(xiě)的通俗易懂测蘑,配圖特別形象灌危。
2.Thread類(lèi)
Thread 狀態(tài):
- 創(chuàng)建(new): 使用new的方式創(chuàng)建一個(gè)Thread對(duì)象。
- 就緒(runnable) :線程對(duì)象調(diào)用start()方法帮寻,進(jìn)入就緒狀態(tài)乍狐,此時(shí)的線程位于可運(yùn)行池中,等待cpu調(diào)度固逗,獲取cpu的使用權(quán)
- 運(yùn)行(running):獲取到cpu的使用權(quán)浅蚪,占用cpu,執(zhí)行run方法中的代碼
- 阻塞(blocked):運(yùn)行狀態(tài)的線程烫罩,遇到阻塞事件惜傲,進(jìn)入阻塞狀態(tài),此時(shí)虛擬機(jī)不會(huì)再給線程分配cpu贝攒。當(dāng)阻塞狀態(tài)解除時(shí)盗誊,進(jìn)入就緒狀態(tài)。
阻塞狀態(tài)又分幾種種情況:1)位于對(duì)象等待池中的阻塞狀態(tài)隘弊,也就是調(diào)用wait()方法哈踱;2)對(duì)象鎖中的阻塞狀態(tài),在使用某個(gè)對(duì)象的同步鎖時(shí)梨熙,該對(duì)象的同步鎖已經(jīng)被其他線程占用开镣;3).使用了sleep()方法或者join()方法
2.1 兩種創(chuàng)建線程的方式
創(chuàng)建一個(gè)線程一共有三種方式,其中兩種常見(jiàn)的是:
- 繼承Thread類(lèi)
- 實(shí)現(xiàn)Runnable接口
2.1.1繼承Thread類(lèi)
class MyThread_1 extends Thread {
public MyThread_1(String name) {
super(name);
}
@Override
public void run() {
System.out.println(System.currentTimeMillis());
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---->" + i);
}
System.out.println(System.currentTimeMillis());
}
}
開(kāi)啟線程就是:
new MyThread_1("thread_1").start()
Thread的構(gòu)造方法有8種咽扇,常見(jiàn)的也就4種:
- Thread()
- Thread(Runnable target)
- Thread(Runnable target, String name)
- Thread(String name)
2.1.2實(shí)現(xiàn)Runnable接口
class Ticket implements Runnable {
private int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
System.out.println("--->" + num--);
}
}
}
}
開(kāi)啟線程就是:
new Thread(new Ticket()).start()
受限于Java單繼承可多實(shí)現(xiàn)的特點(diǎn)邪财,一般都常用第二種方式,往往也是直接采用匿名內(nèi)部類(lèi)质欲。
2.2 Thread類(lèi)的常見(jiàn)方法
- setName(String name) getName()
給Thread設(shè)置自定義的名字树埠。也可以采用構(gòu)造方法傳值。對(duì)應(yīng)的便是拿到Thread的名字嘶伟。如果不設(shè)置線程的名字怎憋,拿到的默認(rèn)名字是"Thread-0,1,2..."。
- currentThread()
獲取當(dāng)前線程
- start()
開(kāi)啟線程九昧。啟動(dòng)線程后绊袋,開(kāi)始執(zhí)行run()方法中的代碼赠橙。
- setPriority(int newPriority) getPriority()
為線程設(shè)置優(yōu)先級(jí)。優(yōu)先級(jí)從1到10愤炸。默認(rèn)是5。
- sleep(long millis)
休眠毫秒數(shù)
- join()
線程加入或者線程合并掉奄,加入的線程執(zhí)行結(jié)束才會(huì)執(zhí)行其他的線程规个。
- yield()
線程讓步,暫停當(dāng)前正在執(zhí)行的線程姓建,把執(zhí)行權(quán)給其他線程诞仓,執(zhí)行其他的線程
- interrupt()
中斷線程。并不是停止一個(gè)線程速兔,給線程加一個(gè)中斷標(biāo)記墅拭。使線程從阻塞(凍結(jié))狀態(tài)到運(yùn)行狀態(tài)。
- isInterrupted()
判斷線程是否中斷成功涣狗。
上面的幾個(gè)方法谍婉,也都是簡(jiǎn)單學(xué)習(xí)。了解并不深入镀钓。
2.3線程全問(wèn)題
上面的比方中說(shuō)穗熬,線程就像車(chē)間的工人,而車(chē)間內(nèi)的房間工人們都可以出入的丁溅。當(dāng)多個(gè)工人同時(shí)工作時(shí)唤蔗,有時(shí)需要使用同一個(gè)房間內(nèi)的原材料,如果多名對(duì)原材料有操作資格的工人同時(shí)都進(jìn)入這個(gè)房間的話窟赏,都同時(shí)對(duì)材料進(jìn)行操作會(huì)存在安全隱患妓柜,這時(shí)就需要考慮給這個(gè)的房間設(shè)置一些限制,只有在沒(méi)人使用原材料的情況下涯穷,有操作資格的工人才可以進(jìn)入棍掐。一旦有一名工人在對(duì)原材料進(jìn)行操作,其他工人就要等待求豫,直到房間內(nèi)對(duì)原材料進(jìn)行操作的工人出來(lái)塌衰,下一名對(duì)原材料有操作資格的工人才可以進(jìn)入。
也就是說(shuō)多線程在一些情況下會(huì)出一些安全情況蝠嘉。
2.3.1同步鎖
當(dāng)多個(gè)線程要同時(shí)操作一個(gè)共享的數(shù)據(jù)最疆,共享數(shù)據(jù)執(zhí)行過(guò)程中還進(jìn)行了變換,其中一個(gè)線程正在對(duì)共享數(shù)據(jù)進(jìn)行操作變換時(shí)蚤告,沒(méi)有執(zhí)行結(jié)束努酸,另外一個(gè)線程就參與進(jìn)來(lái),就會(huì)導(dǎo)致共享數(shù)據(jù)的操作錯(cuò)誤杜恰。這個(gè)安全問(wèn)題其實(shí)就是大名鼎鼎的生產(chǎn)者消費(fèi)者問(wèn)題获诈。
解決的思路就是每次只允許一個(gè)線程進(jìn)行共享數(shù)據(jù)的操作仍源,加入了同步鎖。
public class Thread_2 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t_1 = new Thread(ticket, "one");
Thread t_2 = new Thread(ticket, "two");
t_1.start();
t_2.start();
}
}
class Ticket implements Runnable {
private int num = 100;
@Override
public void run() {
while (true) {
synchronized (Ticket.this) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + num--);
}
}
}
}
}
2.3.2 關(guān)于wait舔涎,notify笼踩,notiryAll
需要注意的是,wait(),notify(),notifyall()
這幾個(gè)方法并不是Thread類(lèi)中的亡嫌,而是Objecet類(lèi)的嚎于。這幾個(gè)方法是根據(jù)需要有時(shí)要用在同步鎖中,同步鎖的對(duì)象是可以為任何對(duì)象的挟冠。在同步代碼塊中于购,調(diào)用這些方法是需要同步鎖對(duì)象的,如果這3個(gè)方法是在Thread類(lèi)中知染,就不符合實(shí)際的需要肋僧,不滿足能夠被任意同步鎖調(diào)用。
貼一個(gè)學(xué)習(xí)時(shí)控淡,敲的代碼:
public class Thread_3 {
public static void main(String[] args) {
Person person = new Person();
new Thread(new Input(person)).start();
new Thread(new Output(person)).start();
}
}
class Input implements Runnable {
private Person person;
public Input(thread_demo.Person person) {
this.person = person;
}
@Override
public void run() {
if (person != null) {
int x = 0;
while (true) {
if (x == 0) {
person.set("Java", "22");
} else {
person.set("李四", "五");
}
x = (x + 1) % 2;
}
}
}
}
class Output implements Runnable {
private Person person;
public Output(Person person) {
this.person = person;
}
@Override
public void run() {
if (person != null) {
while (true) {
person.out();
}
}
}
}
class Person {
private String name;
private String age;
private boolean flag;
public synchronized void set(String name, String age) {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.age = age;
flag = true;
this.notifyAll();
}
public synchronized void out() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "--->" + age);
flag = false;
this.notifyAll();
}
}
2.3.3單例模式-懶漢式 防止多線程創(chuàng)建多個(gè)對(duì)象
public class Singleton {
private static Singleton singleton = null;
private Singleton() {};
public static Singleton getInstance() {
if (singleton == null) {//為了減少對(duì)同步鎖的判斷
synchronized (Singleton.class) {//靜態(tài)方法中沒(méi)有this
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
最后
一直以來(lái)就感覺(jué)多線程是個(gè)很難的東西嫌吠。大三學(xué)習(xí)Java課程老師上課講解生產(chǎn)者消費(fèi)者時(shí),覺(jué)得好復(fù)雜啊逸寓,多線程果然好難居兆。雖然現(xiàn)在工作經(jīng)驗(yàn)不多,但時(shí)隔一年半竹伸,再來(lái)看這個(gè)問(wèn)題時(shí)泥栖,也顯得不那么復(fù)雜了。不過(guò)勋篓,對(duì)于多線程依然覺(jué)得很難吧享。目前對(duì)于線程的狀態(tài)還不是很清晰
在我目前的Android開(kāi)發(fā)經(jīng)驗(yàn)中,對(duì)于多線程的使用不多譬嚣,雖然Android中不允許在UI主線程來(lái)進(jìn)行網(wǎng)絡(luò)請(qǐng)求钢颂,但目前各個(gè)網(wǎng)絡(luò)請(qǐng)求框架都有對(duì)異步線程封裝,結(jié)合Handler便可以實(shí)現(xiàn)對(duì)UI內(nèi)容的更新拜银,顯示網(wǎng)絡(luò)請(qǐng)求拿到的數(shù)據(jù)殊鞭。目前對(duì)Thread的了解比較淺顯。
使用的場(chǎng)景也往往比較簡(jiǎn)單尼桶。記得比較清楚的兩個(gè)場(chǎng)景操灿,一個(gè)開(kāi)啟一個(gè)線程用來(lái)計(jì)時(shí),一個(gè)用于實(shí)現(xiàn)廣告頁(yè)無(wú)限輪播泵督。感覺(jué)這兩個(gè)使用場(chǎng)景也可以用Android中的Handler來(lái)代替趾盐,而且實(shí)現(xiàn)過(guò)程也相對(duì)簡(jiǎn)單。接下來(lái)打算用兩種方式再來(lái)實(shí)現(xiàn)一下在Android中計(jì)時(shí)。然后找本書(shū)救鲤,再深入點(diǎn)學(xué)習(xí)久窟,就自己嘗試一下實(shí)現(xiàn)多線程下載一個(gè)大文件。