多線程
一、多線程中的幾個(gè)概念
1.程序:靜態(tài)的代碼
2.進(jìn)程:正在運(yùn)行的一個(gè)程序 正在使用的QQ捶牢,Android Studio。進(jìn)程用于管理所有的資源,不進(jìn)行實(shí)際的任務(wù)
3.線程:完成具體任務(wù)沽损,QQ運(yùn)行起來就是線程(一個(gè)進(jìn)程里面可以有多個(gè)線程)。運(yùn)行QQ循头,聊天绵估、視頻炎疆、QQ游戲同時(shí)運(yùn)行,這就是一個(gè)個(gè)線程
4.主線程:Java里面国裳,main方法里面的代碼就在主線程中運(yùn)行形入。在手機(jī)里面,我們看到的主界面缝左,就是一個(gè)主線程
5.子線程:除了主線程之外的線程
二亿遂、為什么使用多線程
在主線程里面,任務(wù)的執(zhí)行是從上至下的渺杉,如果其中一個(gè)任務(wù)需要耗費(fèi)大量時(shí)間蛇数。那么這個(gè)任務(wù)后面的任務(wù)就必須等這個(gè)任務(wù)結(jié)束后才能被執(zhí)行,就會形成阻塞少办。這個(gè)時(shí)候就需要將這個(gè)任務(wù)放在另一個(gè)線程里面去執(zhí)行(子線程)
*注:不管是主線程還是子線程都有自己獨(dú)立的執(zhí)行路徑
三苞慢、如何開啟一個(gè)線程
1.寫一個(gè)類繼承于Thread
步驟:
(1)創(chuàng)建類繼承于Thread,具體執(zhí)行的任務(wù)放在run()里面
(2)創(chuàng)建類的對象
(3)調(diào)用start()方法執(zhí)行
注*線程的執(zhí)行是通過搶占時(shí)間片來獲取執(zhí)行機(jī)會的英妓,時(shí)間片是由操作系統(tǒng)來分配的
所以有多個(gè)線程的時(shí)候,每次執(zhí)行的結(jié)果可能不一樣
class TestThread extends Thread{
//1.創(chuàng)建一個(gè)類繼承于Thread
//可以通過重寫構(gòu)造方法給子線程命名
public TestThread(@NonNull String name) {
super(name);
}
@Override
//子類必須實(shí)現(xiàn)父類的run方法绍赛,這個(gè)線程執(zhí)行的任務(wù)在run方法里面
public void run() {
System.out.println(getName());
//也可以用Thread.currentThread()蔓纠,獲取當(dāng)前線程的名字
System.out.println("Hello World");
}
}
public class MyClass {
public static void main(String[] args){
//2.創(chuàng)建具體的對象
TestThread testThread = new TestThread("子線程");//給子線程命名
//3.啟動線程,不調(diào)用start無法啟動線程
testThread.start();
}
}
public static void testRunnable(){
//2.創(chuàng)建具體對象
TestRunnable testRunnable = new TestRunnable();
//3.創(chuàng)建一個(gè)Thread對象 讓這個(gè)線程去執(zhí)行testRunnable的任務(wù)
Thread thread = new Thread(testRunnable);
thread.start();
}
}
2.寫一個(gè)類實(shí)現(xiàn)Runnable接口
步驟:
(1)創(chuàng)建一個(gè)類實(shí)現(xiàn)Runnable接口,但它并不能分配線程
(2)創(chuàng)建一個(gè)該類的對象
(3)創(chuàng)建Thread類的對象來創(chuàng)建線程
(4)調(diào)用start方法開啟線程
class TestRunnable implements Runnable{
//1.創(chuàng)建一個(gè)類實(shí)現(xiàn)Runnable方法
//這個(gè)類不能開啟線程吗蚌,也需要通過Thread來開啟
@Override
public void run() {
System.out.println("Hello World");
}
}
public class MyClass {
public static void main(String[] args){
//2.創(chuàng)建具體對象
TestRunnable testRunnable = new TestRunnable();
//3.創(chuàng)建一個(gè)Thread對象 讓這個(gè)線程去執(zhí)行testRunnable的任務(wù)
Thread thread = new Thread(testRunnable);
thread.start();
}
}
3.兩種啟動方式的對比
第一種創(chuàng)建方法簡單一些腿倚,但是無法實(shí)現(xiàn)多繼承
第二種靈活性更強(qiáng),因?yàn)榻涌诳梢詫?shí)現(xiàn)多繼承
四蚯妇、線程的生命周期
線程的5種形態(tài)
new創(chuàng)建狀態(tài)->start就緒狀態(tài)-><-搶到時(shí)間片,運(yùn)行狀態(tài)(失去時(shí)間片進(jìn)入就緒狀態(tài))->run死亡狀態(tài)
運(yùn)行狀態(tài)中敷燎,可能遇到阻塞狀態(tài)
1.創(chuàng)建狀態(tài)
new Thread()
2.就緒狀態(tài)
(1)調(diào)用start()
(2)阻塞條件結(jié)束
(3)正在運(yùn)行的線程時(shí)間片被其他線程所搶奪
3.運(yùn)行狀態(tài)
從就緒狀態(tài)到運(yùn)行狀態(tài)是由操作系統(tǒng)進(jìn)行,外部無法干預(yù)
4.死亡狀態(tài)
(1)run方法結(jié)束
(2)手動讓線程暫停 stop(不建議使用箩言,通過其他方式暫停)
5.阻塞狀態(tài)
阻塞狀態(tài)分為3種硬贯,同步阻塞synchronized,等待阻塞wait陨收,其他阻塞sleep饭豹,join
五、如何讓一個(gè)線程結(jié)束
(1)使用stop()方法;(此方法不建議使用)
(2)寫一個(gè)變量來標(biāo)識線程結(jié)束
class TestThread extends Thread{
boolean stop = true;
@Override
public void run() {
while (stop) {
System.out.println("子線程");
}
}
public void terminated(){
stop = false;
}//寫一個(gè)終止方法
}
public class MyClass {
public static void main(String[] args){
TestThread t = new TestThread();
t.start();
for(int i = 0;i<20;i++){
if(i==10){
t.terminated();
}
}
}
}
六务漩、線程禮讓和線程插隊(duì)
線程禮讓:yield()
線程插隊(duì):join()
禮讓的線程會直接進(jìn)入就緒狀態(tài)拄衰,如果這個(gè)線程再次獲得時(shí)間片,它還會執(zhí)行饵骨,所以可能禮讓失敗
TestRunnable testRunnable = new TestRunnable();
Thread t1 = new Thread(testRunnable,"子線程1");
t1.start();
for(int i = 0;i<100;i++){
System.out.println("主線程");
if(i == 20){
Thread.yield();
}
}
public class MyClass {
public static void main(String[] args){
TestThread t = new TestThread();
t.start();
}
}
插隊(duì)同理翘悉,插隊(duì)后的線程執(zhí)行完后再執(zhí)行原線程
七、多線程的利弊
1.優(yōu)點(diǎn)
(1)提高執(zhí)行的效率
(2)不會阻塞主線程
2.缺點(diǎn)
(1)多個(gè)線程操作同一個(gè)資源時(shí)居触,有可能會出錯(cuò)
class BuyTickets extends Thread{
static int total = 10;
@Override
public void run() {
for(int i = 1;i<11;i++){
if (total == 0) {
stop();
}
}
total--;
System.out.println("第"+(10-total)+"張票購成功");
}
}
}
public class MyClass {
public static void main(String[] args){
BuyTickets Passenger1 = new BuyTickets();
BuyTickets Passenger2 = new BuyTickets();
BuyTickets Passenger3 = new BuyTickets();
Passenger1.start();
Passenger2.start();
Passenger3.start();
}
}
3.克服缺點(diǎn)的辦法
(1)Lock鎖妖混,代碼塊必須使用同一把鎖
(2)2.synchronized鎖
class BuyTickets extends Thread{
Object object = new Object();//創(chuàng)建一個(gè)臨時(shí)對象
static int total = 10;
@Override
public void run() {
for(int i = 1;i<11;i++){
synchronized (object) {
if (total == 0) {
stop();
}
}
total--;
System.out.println("第"+(10-total)+"張票購成功");
}
}
}
public class MyClass {
public static void main(String[] args){
BuyTickets Passenger1 = new BuyTickets();
BuyTickets Passenger2 = new BuyTickets();
BuyTickets Passenger3 = new BuyTickets();
Passenger1.start();
Passenger2.start();
Passenger3.start();
}
}
*注:鎖的對象必須相同包吝,每一個(gè)對象都維護(hù)一把鎖
不管是鎖代碼塊還是鎖方法,盡量讓鎖的范圍變小
八源葫、線程間的通信
1.實(shí)現(xiàn)線程間通信的三個(gè)方法
(1)wait()讓某個(gè)線程等待
(2)notify()喚醒某個(gè)線程
(3)notifyAll()喚醒所有線程
*注:這三個(gè)方法必須由同步監(jiān)視器(必須被Lock或synchronized包裝的代碼塊)來調(diào)用
public class MyClass {
static intersection section = new intersection();
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
section.printnum();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
section.printlet();
}
}).start();
}
}//匿名對象和匿名內(nèi)部類快速創(chuàng)建對象和調(diào)用方法
class intersection{
int number = 1;
char letter = 'a';
int state = 1;//通過變化state的值來改變線程
public synchronized void printnum(){
while(true){
if(state != 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(number);
number++;
if(number == 27){
break;
}
state = 2;
this.notify();
}
}
public synchronized void printlet(){
while(true){
if(state != 2){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(letter);
letter++;
if(letter == ('z'+1)){
break;
}
state = 1;
this.notify();
}
}
}