第1章 多線程概述
??學(xué)習(xí)多線程之前昏滴,我們先要了解幾個(gè)關(guān)于多線程有關(guān)的概念昼窗。
- A:進(jìn)程:進(jìn)程指正在運(yùn)行的程序蚯妇。確切的來(lái)說(shuō)敷燎,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行暂筝,即變成一個(gè)進(jìn)程,進(jìn)程是處于運(yùn)行過(guò)程中的程序硬贯,并且具有一定獨(dú)立功能焕襟。
- B:線程:線程是進(jìn)程中的一個(gè)執(zhí)行單元,負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行饭豹,一個(gè)進(jìn)程中至少有一個(gè)線程鸵赖。一個(gè)進(jìn)程中是可以有多個(gè)線程的,這個(gè)應(yīng)用程序也可以稱之為多線程程序拄衰。
- C:簡(jiǎn)而言之:一個(gè)程序運(yùn)行后至少有一個(gè)進(jìn)程它褪,一個(gè)進(jìn)程中可以包含多個(gè)線程
??什么是多線程呢?即就是一個(gè)程序中有多個(gè)線程在同時(shí)執(zhí)行翘悉。
第2章 線程實(shí)現(xiàn)
2.1 實(shí)現(xiàn)線程一:繼承Thread類
??該如何創(chuàng)建線程呢列赎?通過(guò)API中搜索,查到Thread類镐确。通過(guò)閱讀Thread類中的描述。Thread是程序中的執(zhí)行線程饼煞。Java虛擬機(jī)允許應(yīng)用程序并發(fā)地運(yùn)行多個(gè)執(zhí)行線程源葫。
- 創(chuàng)建線程的步驟:
- 定義一個(gè)類繼承Thread。
- 重寫(xiě)run方法砖瞧。
- 創(chuàng)建子類對(duì)象息堂,就是創(chuàng)建線程對(duì)象。
- 調(diào)用start方法块促,開(kāi)啟線程并讓線程執(zhí)行荣堰,同時(shí)還會(huì)告訴jvm去調(diào)用run方法
2.1.1 案例代碼
package com.itheima_01;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
package com.itheima_01;
/*
* 多線程的實(shí)現(xiàn)方式:
* 方式1:一種方法是將類聲明為 Thread 的子類。該子類應(yīng)重寫(xiě) Thread 類的 run 方法竭翠。接下來(lái)可以分配并啟動(dòng)該子類的實(shí)例
*
* Thread
* String getName() 返回該線程的名稱振坚。
* void setName(String name) 改變線程名稱,使之與參數(shù) name 相同斋扰。
*
*
* CPU執(zhí)行程序的隨機(jī)性
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//創(chuàng)建線程實(shí)例
MyThread mt = new MyThread();
//修改線程名字
mt.setName("張三");
//啟動(dòng)線程
mt.start();
//創(chuàng)建線程實(shí)例
MyThread mt2 = new MyThread();
mt2.setName("老王");
//啟動(dòng)線程
mt2.start();
}
}
2.2 實(shí)現(xiàn)線程二:實(shí)現(xiàn)Runnable接口
??創(chuàng)建線程的另一種方法是聲明實(shí)現(xiàn) Runnable 接口的類渡八。該類然后實(shí)現(xiàn) run 方法。然后創(chuàng)建Runnable的子類對(duì)象传货,傳入到某個(gè)線程的構(gòu)造方法中屎鳍,開(kāi)啟線程。
??為何要實(shí)現(xiàn)Runnable接口问裕,Runable是啥玩意呢逮壁?繼續(xù)API搜索
。
??查看Runnable接口說(shuō)明文檔:Runnable接口用來(lái)指定每個(gè)線程要執(zhí)行的任務(wù)粮宛。包含了一個(gè) run 的無(wú)參數(shù)抽象方法窥淆,需要由接口實(shí)現(xiàn)類重寫(xiě)該方法卖宠。
- 創(chuàng)建線程的步驟。
- 定義類實(shí)現(xiàn)Runnable接口祖乳。
- 覆蓋接口中的run方法逗堵。。
- 創(chuàng)建Thread類的對(duì)象
- 將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread類的構(gòu)造函數(shù)眷昆。
- 調(diào)用Thread類的start方法開(kāi)啟線程蜒秤。
2.2.1 案例代碼
package com.itheima_02;
public class MyThread2 implements Runnable {
int num;
public MyThread2(int num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//Thread t = Thread.currentThread();
//System.out.println(t.getName() + ":" + i);
//鏈?zhǔn)骄幊? System.out.println(Thread.currentThread().getName() + ":" + i + num);
}
}
}
第3章 多線程安全問(wèn)題產(chǎn)生&解決方案
3.1 多線程賣票案例
??需求:用三個(gè)線程模擬三個(gè)售票窗口,共同賣100張火車票,每個(gè)線程打印出賣第幾張票
3.1.1 案例代碼
package com.itheima_03;
public class TicketThread implements Runnable {
int tickets = 100;//火車票數(shù)量
@Override
public void run() {
//出售火車票
while(true) {
//當(dāng)火車票小于0張,則停止售票
if(tickets > 0) {
/*
* t1,t2,t3
* 假設(shè)只剩一張票
* t1過(guò)來(lái)了亚斋,他一看有票作媚,他就進(jìn)來(lái)了,但是他突然肚子不舒服帅刊,然后他就去上衛(wèi)生間了
* t2也過(guò)來(lái)了纸泡,他一看也有票,他也進(jìn)來(lái)了赖瞒,但是他的肚子也不舒服女揭,他也去上衛(wèi)生間了
*
* t1上完了衛(wèi)生間回來(lái)了,開(kāi)始售票
* tickets = 0;
* t2也上完衛(wèi)生間回來(lái)了栏饮,他也進(jìn)行售票
* tickets = -1;
*
*
*/
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" +tickets--);
}
}
}
}
3.2 多線程安全問(wèn)題解決
3.2.1 使用同步代碼塊解決
格式:
synchronized(鎖對(duì)象){
//需要同步的代碼
}
3.2.1.1案例代碼四:
package com.itheima_04;
/*
* 問(wèn)題出現(xiàn)的原因:
* 要有多個(gè)線程
* 要有被多個(gè)線程所共享的數(shù)據(jù)
* 多個(gè)線程并發(fā)的訪問(wèn)共享的數(shù)據(jù)
*
* 在火車上上廁所
* 張三來(lái)了吧兔,一看門是綠的,他就進(jìn)去了袍嬉,把門鎖上了境蔼,門就變紅了
* 李四來(lái)了,一看門市紅色的伺通,他就只能憋著
* 張三用完了廁所箍土,把鎖打開(kāi)了,門就變成了綠色
* 李四一看門變綠了罐监,他就進(jìn)去了吴藻,把門鎖上,門就變紅了
* 王五來(lái)了弓柱,一看們是紅色的调缨,他也只能憋著
* 李四用完測(cè)試了,把鎖打開(kāi)了吆你,肚子又不舒服了弦叶,扭頭回去了,又把門鎖上了妇多,
*
* synchronized:同步(鎖)伤哺,可以修飾代碼塊和方法,被修飾的代碼塊和方法一旦被某個(gè)線程訪問(wèn),則直接鎖住立莉,其他的線程將無(wú)法訪問(wèn)
*
* 同步代碼塊:
* synchronized(鎖對(duì)象){
*
* }
*
* 注意:鎖對(duì)象需要被所有的線程所共享
*
*
* 同步:安全性高绢彤,效率低
* 非同步:效率高,但是安全性低
*
*/
public class TicketThread implements Runnable {
int tickets = 100;//火車票數(shù)量
Object obj = new Object();
@Override
public void run() {
//出售火車票
while(true) {
synchronized (obj) {
if(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" +tickets--);
}
}
}
}
}
package com.itheima_04;
public class TicktetTest {
public static void main(String[] args) {
//創(chuàng)建線程對(duì)象
TicketThread tt = new TicketThread();
Thread t = new Thread(tt);
t.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");
//啟動(dòng)線程對(duì)象
t.start();
t2.start();
t3.start();
}
}
3.2.2 使用同步方法解決
格式:
修飾符 synchronized 返回值 方法名(){
}
3.2.2.1 案例代碼
package com.itheima_05;
/*
* 同步方法:使用關(guān)鍵字synchronized修飾的方法,一旦被一個(gè)線程訪問(wèn),則整個(gè)方法全部鎖住株憾,其他線程則無(wú)法訪問(wèn)
*
* synchronized
* 注意:
* 非靜態(tài)同步方法的鎖對(duì)象是this
* 靜態(tài)的同步方法的鎖對(duì)象是當(dāng)前類的字節(jié)碼對(duì)象
*/
public class TicketThread implements Runnable {
static int tickets = 100;// 火車票數(shù)量
Object obj = new Object();
@Override
public void run() {
// 出售火車票
while (true) {
/*synchronized (obj) {
method();
}*/
//method();
method2();
}
}
private synchronized void method() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
}
private static synchronized void method2() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
}
}
package com.itheima_05;
public class TicktetTest {
public static void main(String[] args) {
//創(chuàng)建線程對(duì)象
TicketThread tt = new TicketThread();
Thread t = new Thread(tt);
t.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");
//啟動(dòng)線程對(duì)象
t.start();
t2.start();
t3.start();
}
}