大家可以關(guān)注作者的賬號(hào),關(guān)注從零開始學(xué)Java筆記文集。也可以根據(jù)目錄前往作者的博客園博客進(jìn)行學(xué)習(xí)。本片文件將基于黑馬程序員就業(yè)班視頻進(jìn)行學(xué)習(xí)以及資料的分享,并記錄筆記和自己的看法。歡迎大家一起學(xué)習(xí)和討論。
【從零開始學(xué)Java筆記】目錄
進(jìn)程和線程的區(qū)別
進(jìn)程:是執(zhí)行中一段程序,即一旦程序被載入到內(nèi)存中并準(zhǔn)備執(zhí)行,它就是一個(gè)進(jìn)程。進(jìn)程是表示資源分配的的基本概念,又是調(diào)度運(yùn)行的基本單位,是系統(tǒng)中的并發(fā)執(zhí)行的單位。
線程:?jiǎn)蝹€(gè)進(jìn)程中執(zhí)行中每個(gè)任務(wù)就是一個(gè)線程。線程是進(jìn)程中執(zhí)行運(yùn)算的最小單位。
一個(gè)進(jìn)程可以有一個(gè)線程,也可以有多個(gè)線程
單線程和多線程的優(yōu)缺點(diǎn)
單線程:安全性高,但是效率低
多線程:安全性低,效率高
多線程案例: 360,迅雷等
Thread類
創(chuàng)建線程的方法一
先創(chuàng)建一個(gè)類,繼承Thread類,并且重寫run方法。
public class MyThread extends Thread{
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+i);
}
}
}
在主函數(shù)里面調(diào)用MyThread類,然后使用start()方法即可調(diào)用。
public class ThreadDemo {
public static void main(String[] args) {
//創(chuàng)建一個(gè)線程
MyThread mt = new MyThread();
mt.setName("Thread-1:");
mt.start();
//創(chuàng)建另一個(gè)線程
MyThread mt2 = new MyThread();
mt2.setName("Thread-2:");
mt2.start();
}
}
輸出結(jié)果(部分)如下

可以看出線程一和線程二交替出現(xiàn),這是因?yàn)橛?jì)算機(jī)處理兩個(gè)同時(shí)運(yùn)行的線程時(shí),用極短的時(shí)間在兩個(gè)線程間切換,隨機(jī)調(diào)用。但速度極快,無(wú)法察覺到。
創(chuàng)建線程的方法二
創(chuàng)建線程的另一種方法是聲明實(shí)現(xiàn) Runnable 接口的類。該類然后實(shí)現(xiàn) run 方法。然后可以分配該類的實(shí)例,在創(chuàng)建 Thread 時(shí)作為一個(gè)參數(shù)來(lái)傳遞并啟動(dòng)。
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
MyThread2 mt = new MyThread2();
Thread t = new Thread(mt);
t.setName("Thread-1:");
t.start();
MyThread2 mt2 = new MyThread2();
Thread t2 = new Thread(mt);
t2.setName("Thread-2:");
t2.start();
}
}
輸出結(jié)果(部分)與之前的輸出結(jié)果相似

模擬火車站售票
1.先創(chuàng)建線程類
要求:100張票,持續(xù)售票,票數(shù)為0停止售票。
public class ThreadTicket implements Runnable {
private static int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum > 0) {
System.out.println(Thread.currentThread().getName() + ":" + ticketNum--);
}
}
}
public static int getTicketnum() {
return ticketNum;
}
}
主函數(shù)
public class ThreadTicketDemo {
public static void main(String[] args) {
ThreadTicket tt = new ThreadTicket();
Thread t1 = new Thread(tt);
t1.setName("售票口1");
t1.start();
}
}
輸出結(jié)果(部分)

火車站不可能只有一個(gè)售票口,所以多加幾個(gè)線程
public class ThreadTicketDemo {
public static void main(String[] args) {
ThreadTicket tt = new ThreadTicket();
Thread t1 = new Thread(tt);
t1.setName("售票口1");
t1.start();
Thread t2 = new Thread(tt);
t2.setName("售票口2");
t2.start();
Thread t3 = new Thread(tt);
t3.setName("售票口3");
t3.start();
}
}
輸出結(jié)果(部分)

細(xì)心的朋友應(yīng)該發(fā)現(xiàn)最后售票是十分不規(guī)律的,例如售票口2已經(jīng)在售賣最后一張票的時(shí)候,售票口3還在售賣第十一張票,其實(shí)這是符合生活規(guī)律的,大家思考一下自己買票的經(jīng)歷。因?yàn)閏pu調(diào)用每個(gè)線程都是隨機(jī)的,而線程的運(yùn)行也是需要時(shí)間的,售票口3在售賣第十一張票的時(shí)候,cpu不斷調(diào)用售票口2,持續(xù)售票。
為了讓程序更明顯一點(diǎn),我們?cè)诔绦蛑屑尤雜leep()
public class ThreadTicket implements Runnable {
private static int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticketNum--);
}
}
}
public static int getTicketnum() {
return ticketNum;
}
}
再次輸出

首先可以發(fā)現(xiàn)程序明顯變慢,然后發(fā)現(xiàn)有一些錯(cuò)誤,第六張票售出兩次,并且沒有票的時(shí)候還售出一張,這是因?yàn)楫?dāng)票數(shù)還是正常的情況下,例如一張,其中一個(gè)線程進(jìn)行判斷,并進(jìn)入休息。另一個(gè)進(jìn)程也進(jìn)入判斷。這時(shí)兩個(gè)進(jìn)程同時(shí)通過判斷,休息后,同時(shí)進(jìn)行了輸出,就會(huì)出現(xiàn)這樣的錯(cuò)誤。
那么如何解決這個(gè)問題呢?
synchronized:同步(鎖),可以修飾代碼塊和方法,被修飾的代碼塊和方法一旦被某個(gè)線程訪問,則直接鎖住,其他的線程將無(wú)法訪問。
方法一:
同步代碼塊:
synchronized(鎖對(duì)象){
}
注意:鎖對(duì)象需要被所有的線程所共享
方法二:
同步方法:使用關(guān)鍵字synchronized修飾的方法,一旦被一個(gè)線程訪問, 則整個(gè)方法全郁鎖住,其他線程則無(wú)法訪問。
注意:
非靜態(tài)同步方法的鎖對(duì)象是this
靜態(tài)的同步方法的鎖對(duì)象是當(dāng)前類的字節(jié)碼對(duì)象
在這里如果遇到了加鎖后出現(xiàn)了只有一個(gè)線程運(yùn)行的情況,可以參考
synchronized同步方法\塊只有一個(gè)線程執(zhí)行\(zhòng)運(yùn)行
同步的特點(diǎn):
同步:安全性高,效率低
非同步:效率高,但是安全性低