java多线程--多线程基础小结
什么是線程?
?? 在同一個(gè)進(jìn)程中可以執(zhí)行多個(gè)任務(wù),每一個(gè)任務(wù)可以看做一個(gè)線程.
? 線程是程序的執(zhí)行單元,執(zhí)行路徑,使程序使用cpu的最基本單位
? 一個(gè)進(jìn)程如果只有一條執(zhí)行路徑,那么就是單線程的
一個(gè)進(jìn)程如果有多個(gè)執(zhí)行路徑,那么就是多線程的
多線程的意義:
?? 多進(jìn)程的存在是為了提高CPU的利用率,多線程的存在,不是為了提高程序的執(zhí)行速度,而是為了提高應(yīng)用程序的使用率.
?? 程序的執(zhí)行其實(shí)都是在搶CPU的資源,即CPU的執(zhí)行權(quán).
?? 多個(gè)進(jìn)程在搶這個(gè)資源,而其中一個(gè)進(jìn)程如果有多個(gè)執(zhí)行路徑,即多個(gè)線程,那么就有更高的幾率搶到CPU的執(zhí)行權(quán)
JVM的啟動(dòng)是單線程的還是多線程的?
? 是多線程的,啟動(dòng)jvm是就相當(dāng)于啟動(dòng)了一個(gè)進(jìn)程,接著該進(jìn)程會(huì)創(chuàng)建一個(gè)主線程來調(diào)用main方法,而同時(shí)還有啟動(dòng)其他的線程,比如說垃圾回收的線程.
兩種方式實(shí)現(xiàn)多線程,一種是繼承Thread類,一種是實(shí)現(xiàn)Runnable接口.以第一種為例:
package com.wang.reflect; class MyThread extends Thread{ @Override public void run() { for(int i=0;i<10;i++){ //獲取當(dāng)前正在執(zhí)行的線程的名稱,相當(dāng)于this.getName(); System.out.println(Thread.currentThread().getName()+"::"+i); } } } public class TestReflect { public static void main(String[] args) throws Exception { MyThread mThread1=new MyThread(); MyThread mThread2=new MyThread(); //設(shè)置線程的名字,也可以直接通過帶參構(gòu)造函數(shù)設(shè)置線程名稱 mThread1.setName("張無忌:"); mThread2.setName("趙敏:"); mThread1.start(); mThread2.start(); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"::"+i); } } }一般我們使用第二種方式實(shí)現(xiàn)多線程,實(shí)現(xiàn)Runnable接口的方式相比較繼承Thread類的方式有兩個(gè)好處:
??? 1.避免由于java單繼承帶來的局限性.
?? 2.適合多個(gè)相同程序的代碼去處理同一個(gè)資源的情況,把線程同程序的代碼,數(shù)據(jù),有效分離,較好地體現(xiàn)了面向?qū)ο蟮乃枷?
線程調(diào)度:
線程有兩種調(diào)度模型:
??? 1.分式調(diào)度模型,所有的線程輪流使用CPU的使用權(quán),平均分配每個(gè)線程占用的CPU時(shí)間片.
??? 2.搶占式調(diào)度模型,優(yōu)先讓優(yōu)先級(jí)高的線程先執(zhí)行,如果優(yōu)先級(jí)相同,那么隨機(jī)選擇一個(gè)線程,優(yōu)先級(jí)高的線程獲得CPU的執(zhí)行權(quán)的幾率大一些,并不是嚴(yán)格按照優(yōu)先級(jí)由高到低執(zhí)行.
java使用的是搶占式調(diào)度模型.
方法介紹:
| 獲取當(dāng)前線程對(duì)象的優(yōu)先級(jí),默認(rèn)優(yōu)先級(jí)為5,范圍是1~10 | int? getPriority() |
| 設(shè)置當(dāng)前線程的優(yōu)先級(jí),范圍1~10 | ?? void? setPriority() |
| 在指定毫秒內(nèi)讓正在運(yùn)行的線程休眠(暫停執(zhí)行) | static void sleep(1000) |
| ----------------------------------------------------- | ----------------------------------- |
| 等待當(dāng)前線程執(zhí)行結(jié)束,該方法一般緊隨start()方法之后 | final void join() |
| 暫停當(dāng)前正在執(zhí)行的對(duì)象,執(zhí)行其他線程,(禮讓線程) | static void yield() |
| 將該線程標(biāo)記為守護(hù)線程或用戶線程。 當(dāng)正在運(yùn)行的線程都是守護(hù)線程時(shí),Java 虛擬機(jī)退出。 該方法必須在啟動(dòng)線程前調(diào)用。 | final void setDaemon(true) |
看這張圖,來更好的理解守護(hù)線程:
假設(shè)現(xiàn)在有三個(gè)線程分別叫關(guān)羽,劉備,張飛.將關(guān)羽,張飛設(shè)置為守護(hù)線程,一旦劉備這個(gè)線程執(zhí)行完畢(死掉),那么關(guān)羽,張飛也就沒有活著的意義了(不再執(zhí)行,退出).
| 中斷線程,把線程狀態(tài)終止,并拋出一個(gè)InterruptedException | ??????? void interrupt() |
線程的生命周期:
案例練習(xí):
電影院賣票,假設(shè)有三個(gè)窗口同時(shí)賣票,共有100張票,用多線程模擬這個(gè)過程(要解決的關(guān)鍵問題是:線程同步).
package com.wang.reflect; class SellTicket implements Runnable { // 共享變量 private int tickets = 100; // 創(chuàng)建鎖對(duì)象 private Object obj = new Object(); @Override public void run() { while (true) { /** * 解決線程同步問題的 方案: * synchronized(鎖對(duì)象){ * //操作共享變量的代碼 * } * */ synchronized (obj) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票"); } try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); t1.start(); t2.start(); t3.start(); } }打印結(jié)果如下:
synchronized是一個(gè)關(guān)鍵字.可以加在一段代碼外部,也可以放在一個(gè)方法前.它的鎖對(duì)象可以是任意對(duì)象,當(dāng)然也可以寫成當(dāng)前類的對(duì)象this.
- 同步代碼塊的鎖對(duì)象:任意對(duì)象
- 同步方法的鎖對(duì)象:當(dāng)前對(duì)象this
- 同步靜態(tài)方法的鎖對(duì)象:當(dāng)前類的字節(jié)碼對(duì)象,類名.class;
?
java類庫(kù)中線程安全的類有:StringBuffer,HashTable,Vector.
注意Vector是線程安全的,但是當(dāng)我們需要一個(gè)線程安全的集合的時(shí)候,我們一般也不用它,在Collections類中獲取線程安全的list集合的方法,假設(shè)我們現(xiàn)在需要獲得一個(gè)線程安全的List<String>集合,可以這樣寫:
List<String> list=Collections.synchronizedList(new ArrayList<String>());
?
???? 雖然說我們可以理解同步代碼塊和同步方法的鎖對(duì)象問題,但我們并沒有直接看到在哪兒加上鎖又在哪兒釋放鎖,為了更清晰的表達(dá)如何加鎖和釋放鎖,JDK1.5以后提供了一個(gè)新的鎖對(duì)象Lock.下面通過Lock鎖實(shí)現(xiàn)上述賣票的過程.
package com.wang.reflect; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class SellTicket implements Runnable { // 共享變量 private int tickets = 100; // Lock是一個(gè)接口,ReentrantLock是他的一個(gè)實(shí)現(xiàn)類 private Lock lock=new ReentrantLock(); @Override public void run() { while (true) { lock.lock();//加鎖 if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票"); } lock.unlock();//釋放鎖 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); t1.start(); t2.start(); t3.start(); } }線程同步有兩個(gè)很明顯的弊:一是效率低,二是容易產(chǎn)生死鎖.
死鎖問題:
兩個(gè)或兩個(gè)以上的線程,在爭(zhēng)奪資源的過程中,發(fā)生的一種相互等待的過程.下面通過代碼演示死鎖問題的存在:
package com.wang.reflect; class DieLock extends Thread{ private boolean flag; public DieLock(boolean flag){ this.flag=flag; } public static final Object o1=new Object(); public static final Object o2=new Object(); @Override public void run() { if(flag){ synchronized(o1){ System.out.println("if o1"); synchronized (o2) { System.out.println("if o2"); } } }else{ synchronized (o2) { System.out.println("else o2"); synchronized (o1) { System.out.println("else o1"); } } } } } public class DieLockDemo { public static void main(String[] args) { DieLock dl1=new DieLock(true); DieLock dl2=new DieLock(false); dl1.start(); dl2.start(); } }細(xì)讀代碼,可以發(fā)現(xiàn),理想狀態(tài)下,應(yīng)該會(huì)打印:
if o1
if o2
else o2
else o1
但是運(yùn)行之后,發(fā)現(xiàn)打印結(jié)果大多為:
if o1???????????? 或者???? else o2
else o2??????????????????? if? o1
其實(shí)這里就發(fā)生了線程互相等待,也就是死鎖問題.
生產(chǎn)者-消費(fèi)者模式描述:
package com.wang.reflect; //學(xué)生實(shí)體類作為共享資源 class Student{ String name;//姓名 int age;//年齡 boolean flag;//標(biāo)記變量,判斷當(dāng)前學(xué)生對(duì)象是否已創(chuàng)建賦值好 } //模擬生產(chǎn)者線程類 class SetStudent implements Runnable{ //共享資源s private Student s; private int x=0; public SetStudent(Student s) { this.s=s; } @Override public void run() { while(true){ synchronized (s) { if(s.flag){ try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if(x%2==0){ s.name="郭靖"; s.age=24; }else{ s.name="黃蓉"; s.age=18; } x++; //生產(chǎn)完之后,將標(biāo)記置為true,通知消費(fèi)者來消費(fèi) s.flag=true; //喚醒線程 s.notify(); } } } } //模擬消費(fèi)者線程類 class GetStudent implements Runnable{ //共享資源s private Student s; public GetStudent(Student s) { this.s=s; } @Override public void run() { while(true){ synchronized (s) { if(!s.flag){ try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name+":::"+s.age); //消費(fèi)完以后將標(biāo)記之置于false,通知生產(chǎn)者來生產(chǎn) s.flag=false; //喚醒線程 s.notify(); } } } } //測(cè)試類 public class StudentDemo { public static void main(String[] args) { Student s =new Student(); SetStudent ss=new SetStudent(s); GetStudent gs=new GetStudent(s); Thread t1=new Thread(ss, "生產(chǎn)者"); Thread t2=new Thread(gs, "消費(fèi)者"); t1.start(); t2.start(); } }在上面的代碼中,生產(chǎn)者就是代表對(duì)Student屬性進(jìn)行賦值,消費(fèi)者就是代表對(duì)Studnet屬性進(jìn)行打印,代碼設(shè)置了等待喚醒機(jī)制.
上述代碼可以優(yōu)化的,可以講生產(chǎn)者的賦值行為,和消費(fèi)者的打印行為,封裝到Student類中,寫成兩個(gè)同步方法.實(shí)現(xiàn)這兩個(gè)功能.代碼如下:
package com.wang.reflect; //學(xué)生實(shí)體類作為共享資源 class Student { private String name;// 姓名 private int age;// 年齡 boolean flag;// 標(biāo)記變量,判斷當(dāng)前學(xué)生對(duì)象是否已創(chuàng)建賦值好 public synchronized void set(String name, int age) { if (this.flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name; this.age = age; this.flag = true; this.notify(); } public synchronized void get() { if (!this.flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name + ":::" + age); this.flag = false; this.notify(); } } // 模擬生產(chǎn)者線程類 class SetStudent implements Runnable { // 共享資源s private Student s; private int x = 0; public SetStudent(Student s) { this.s = s; } @Override public void run() { while (true) { if (x % 2 == 0) { s.set("郭靖", 27); } else { s.set("黃蓉", 18); } x++; } } } // 模擬消費(fèi)者線程類 class GetStudent implements Runnable { // 共享資源s private Student s; public GetStudent(Student s) { this.s = s; } @Override public void run() { while (true) { s.get(); } } } // 測(cè)試類 public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetStudent ss = new SetStudent(s); GetStudent gs = new GetStudent(s); Thread t1 = new Thread(ss, "生產(chǎn)者"); Thread t2 = new Thread(gs, "消費(fèi)者"); t1.start(); t2.start(); } }轉(zhuǎn)載于:https://www.cnblogs.com/fingerboy/p/5347314.html
總結(jié)
以上是生活随笔為你收集整理的java多线程--多线程基础小结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java联网技术之一HTTP
- 下一篇: PHP开发APP接口(二)