JAVA多執行緒基礎入門
本篇部落格主要通過以下幾個方面向大家闡述JAVA中的多執行緒內容,可能篇幅比較長,希望大家能有耐心的讀下去。
在實際應用中, 多執行緒非常有用。例如, 一個瀏覽器可以同時下載幾幅圖片。一個 Web伺服器需要同時處理幾個併發的請求。
程式 、程序、執行緒分別是什麼,及其他們的包含關係
Java實現多執行緒三種方式
執行緒的狀態和方法
執行緒基本資訊和優先順序
執行緒的同步和死鎖問題
生產者消費者模式
任務排程
執行緒概念:
程式(指令集):Program,是一個靜態的概念
程序(排程程式):Process,是一個動態的概念,作業系統排程程式
- 程序是程式的一次動態的執行過程,佔用特定的地址空間。
- 每個程序都是獨立的,由3部分組成cpu,data,code
- 缺點:記憶體的浪費,cpu的負擔。
執行緒(程序內多條執行路徑):Thread,是程序中一個“單一的連續控制流程“(a single sequential flow of control)/執行路徑
- 執行緒又被稱為輕量級程序(lightweight process)
- Threads run at the same time,independently of one another.
- 一個程序可擁有多個並行的執行緒
- 一個程序中的執行緒共享相同的記憶體單元/記憶體地址空間,可以訪問相同的變數和物件,而且他們從同一堆中分配物件,通訊、資料交換、同步操作
- 由於執行緒間的通訊是在同一地址空間上進行的,所以不需要額外的通訊機制,這就使得通訊更簡單而且資訊傳遞速度也更快。
執行緒和程序的區別:
- 根本區別:程序作為資源分配的單位。執行緒作為排程和執行的單位。
- 開銷:每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會有較大的開銷。執行緒可以看成是輕量級的程序,同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器,執行緒切換開銷較小。
- 所處環境:程序在作業系統中能同時執行多個任務(程式)。執行緒在同一應用程式總有多個順序流同時執行。
- 分配記憶體:系統在執行的時候會為每個程序分配不同的記憶體區域。執行緒除了CPU之外,不會為執行緒分配記憶體(執行緒所使用的資源是它所屬的程序的資源),執行緒組只能共享資源。
- 包含關係:沒有執行緒的程序是可以被看做單執行緒的,如果一個程序內擁有多個執行緒,則執行過程不是一條直線的,而是多條線(執行緒)共同完成。執行緒是程序的一部分,所以執行緒有的時候被稱為是輕量級程序或輕權程序。
如果有想更加深入的瞭解程序和CPU原理的的可以檢視相關作業系統原理的書籍。
靜態代理設計模式
在介紹建立執行緒的方法之前,首先給大家介紹一個設計模式-------靜態代理設計模式。可能很多人都用過這個設計模式,但是僅是自己不知道而已。其實線上程的建立過程中,就用到了這個設計模式。任何一個學過JAVA的人可能對執行緒的建立及使用都不會陌生。
我首先給大家介紹一下靜態代理設計模式,此模式由三部分組成,分別是真實角色,代理角色,和兩者共同實現的介面。該模式並不是很難理解。打個比方說,我們找房子的時候,大部分時候需要通過中介公司找到適合自己房子,其實中介就扮演著一個代理的角色,與房東進行溝通。再比方說某人結婚的時候,也是需要通過婚慶公司完成婚前婚後一些事物的處理等等,而婚慶公司就是一個代理。現在大家應該對靜態代理設計模式在概念上有了一個初步認識與理解。下面我將從程式碼給大家詳細的解釋什麼靜態代理模式。
首先我們需要定義一個介面Marry,並且在介面中定義marry()方法。分別建立扮演真實角色的類和扮演代理角色的類並且都是要實現剛才定義的介面,重寫marry()方法。然後在代理類中再增加一些你需要的方法即可。那麼靜態代理模式就基本上寫好了。沒錯,就是這麼簡單。下面是具體的程式碼,如有什麼疑問,請給我留言吧。
/**
* 靜態代理設計模式
* 1. 真是角色
* 2. 代理角色:持有真實角色的引用
* 3. 二者實現相同的介面
*/
public class StaticProxy {
public static void main(String[] args) {
You you=new You();
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.marry();
}
}
//介面
interface Marry{
void marry();
}
//真實角色
class You implements Marry{
@Override
public void marry() {
System.out.println("you and 嫦娥結婚了。。。。");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you;
public WeddingCompany() {
}
public WeddingCompany(Marry you) {
this.you = you;
}
private void before(){
System.out.println("佈置洞房....");
}
private void after(){
System.out.println("鬧洞房.....");
}
@Override
public void marry() {
before();
you.marry();
after();
}
}
在JAVA中實現多執行緒的方式有三種,第一種是繼承Thread類;第二種是實現Runnable介面,第三種實現Callable介面。我認為最常用的方式是第二種。java語言中是單繼承,多實現,所以第一種實現多執行緒方式的弊端就顯而易見了,如果某類繼承了Thread類就不能再繼承任何其他類了。第三種方式實現多線比較的繁瑣,但是call()方法可以拋異常,並且有返回值。這是前兩種所沒有的優勢吧。
在java中負責執行緒的這個功能的是java.lang.Thread這個類,而且這個類也是繼承Runnable介面的。
- 可以通過建立Thread的例項來建立新的執行緒
- 每個執行緒都是通過某特定Threa物件所對應的方法run()來完成其操作的,所以方法run()稱為執行緒體
- 通過呼叫Thread類的start()方法來啟動一個執行緒
- 繼承Threa類方式的缺點:那就是如果我們的類已經從一個類繼承,則無法再繼承Threa類
通過Runnab介面實現多執行緒(推薦)
- 優點:可以同時實現繼承。實現Runnable介面方式更通用一些,避免了單繼承
- 方便共享資源,同一份資源多個代理訪問
- 使用方法:類實現Runnable介面,重寫run()方法(真實角色類)
啟動多執行緒,使用靜態代理
建立真實角色
建立代理角色+真實角色引用
呼叫start()啟動執行緒
具體程式碼
public class ProgrammerApp {
public static void main(String[] args) {
Programmer programmer=new Programmer();
Thread t = new Thread(programmer);
t.start();
for (int i=0;i<1000;i++){
System.out.println("聊天............");
}
}
}
public class Programmer implements Runnable {
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("寫程式碼...........");
}
}
}
上面程式碼是通過實現Runnable介面來建立執行緒的,而且你會感覺到你在不知不覺中使用了------靜態代理的設計模式。為什麼這麼說呢?請大家仔細看上面的程式碼,Programmer實現了Runnable介面,剛才在上面我已經說過Thread類也實現了Runnable介面,這就符合靜態代理的第一個要求-----實現同一個介面。在main()方法中我們將Programmer的例項作為引數傳入到了Thread類中,所以我們的Programmer扮演真實角色,Thread扮演代理角色。最後呼叫Thread類的start()方法啟動執行緒。
這裡所說的啟動並非一執行程式碼,該執行緒就開始運行了。因為執行緒是不是執行,要看CPU什麼時候能調到它,並且給它分配時間片,這時它才會執行run()方法中的任務程式碼,當所分配的給某執行緒的時間用完了,該執行緒就進入了非活動狀態
通過Callable介面實現多執行緒
優點:可以獲取返回值
缺點:繁瑣
Callable和Future介面
Callable是類似與Runnable的介面,實現Callable介面的類和實現Runnable的類都是可被其他執行緒執行的任務;
Callable和Runnable有幾點不同:
- Callable規定的方法是call(),而Runnable規定實現的方法是run(0;
- Call()方法可以丟擲異常,而run()方法不能丟擲異常
- Callable的任務執行後可返回值,執行Callable任務可拿到一個Future物件,而Runnable的任務是不能返回值的。Future表示非同步計算的結果,它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。通過Future物件可連線任務執行的情況,可取消任務的執行,還可獲取任務執行的結果
具體思路:
- 建立Callable實現類+重寫call()方法
- 藉助執行排程服務 ExecutorService,獲取Future物件
ExecutorService ser = Executors.newFixedThreadPool(2);
Future<Integer> result=ser.submit(tortoise);
int num=result.get();
ser.shutdownNow();
具體程式碼如下
public class Call {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//建立執行緒
ExecutorService ser = Executors.newFixedThreadPool(2);
Race tortoise=new Race("烏龜",1000);
Race rabbit = new Race("兔子", 500);
//獲取值
Future<Integer> result=ser.submit(tortoise);
Future<Integer> result2=ser.submit(rabbit);
Thread.sleep(5000);
//停止執行緒體迴圈
tortoise.setFlag(false);
rabbit.setFlag(false);
int num=result.get();
int num2=result2.get();
System.out.println(num);
System.out.println(num2);
//停止服務
ser.shutdownNow();
}
}
class Race implements Callable<Integer>{
private String name;
private long time;
private boolean flag=true;
private int step=0;
public Race() {
}
public Race(String name) {
this.name = name;
}
public Race(String name, long time) {
this.name = name;
this.time = time;
}
@Override
public Integer call() throws Exception {
while (flag){
Thread.sleep(time);
step++;
}
return step;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getStep() {
return step;
}
public void setStep(int step) {
this.step = step;
}
}
執行緒的狀態
- 新生狀態----New
- 就緒狀態----Runnable
- 執行狀態
- 阻塞狀態----Blocked
阻塞執行緒的方式有如下三種
(1)join()等待該執行緒終止
/**
* join合併執行緒
*/
public class JoinDemo extends Thread {
public static void main(String[] args) throws InterruptedException {
JoinDemo demo = new JoinDemo();
Thread t = new Thread(demo);//新生
t.start();//就緒
//cpu排程
for (int i=0;i<100;i++){
if (50==i){
//阻塞主執行緒
t.join();
}
System.out.println("Main....."+i);
}
}
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("Join.....");
}
}
}
(2)yield()暫停當前正在執行的執行緒物件,並執行其他執行緒。(靜態方法)
public class YieldDemo extends Thread{
public static void main(String[] args) {
YieldDemo demo=new YieldDemo();
Thread t = new Thread(demo);
t.start();
for (int i=0;i<1000;i++){
if (i%20==0){
//暫停本執行緒
Thread.yield();
}
System.out.println("main..."+i);
}
}
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("Yield........"+i);
}
}
}
(3)sleep()在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。而且休眠,並不釋放鎖
5. 死亡狀態------Terminated
具體詳細解釋
執行緒的基本資訊
- isAlive(),判斷執行緒是否還是活動狀態,即執行緒是否還未終止
- getPriority(),獲得執行緒優先順序的數值
- setPriority(),設定執行緒優先順序數值
- setName(),給執行緒一個名字
- getName(),取得執行緒的名字
- currentThread(),取得當前正在執行的執行緒物件也就是取得自己本身
執行緒的優先順序:優先順序並不代表執行的順序,而是代表的是概率,不是絕對的優先順序,只是概率大的執行的多一點。
執行緒的同步(併發): 多個執行緒訪問同一份資源,所以要確保資源的安全------執行緒安全
Synchronized-->同步
1. 同步塊
Synchronized(引用型別|this|類.class){
}
//同步塊
public void test3() {
synchronized (this) {
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到了" + num--);
System.out.println("wpn");
}
}
具體程式碼
2. 同步方法
//同步方法
public synchronized void test2(){
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到了" + num--);
}
完整程式碼
public class SyncDemo {
public static void main(String[] args) {
//真實角色
Web12306 web = new Web12306();
//代理
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黃牛乙");
Thread t3 = new Thread(web,"攻城獅");
t1.start();
t2.start();
t3.start();
}
}
public class Web12306 implements Runnable{
private int num=1000;
private boolean flag=true;
@Override
public void run() {
while (flag){
test2();
}
}
//同步塊
public void test3() {
synchronized (this) {
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到了" + num--);
System.out.println("wpn");
}
}
//同步方法
public synchronized void test2(){
if (num <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "搶到了" + num--);
}
}
3. 死鎖:過多的同步會造成死鎖,
public class SyncDemo2 {
public static void main(String[] args) {
Object g=new Object();
Object m=new Object();
Test t1=new Test(g,m);
Test2 t2 = new Test2(g,m);
Thread t = new Thread(t1);
Thread t3 = new Thread(t2);
t.start();
t3.start();
}
}
class Test implements Runnable{
Object goods;
Object money;
public Test(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true){
test();
}
}
public void test(){
synchronized (goods){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money){
}
}
System.out.println("一手交貨");
}
}
class Test2 implements Runnable{
Object goods;
Object money;
public Test2(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true){
test();
}
}
public void test(){
synchronized (money){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods){
}
}
System.out.println("一手交錢");
}
}
生產者/消費者模式簡介用來幹嘛的?生產者/消費者模式的產生主要目的就是為了解決非同步的生產與消費之間的問題。什麼是非同步呢?比如我剛剛生產了某個產品,而此時你正在打遊戲,沒空來取,要打完遊戲來取,這就導致了我生產產品和你取產品是兩個非同步的動作,你不知道我什麼時候生產完產品,而我也不知道你什麼時候來取。而生產者/消費者模式就是解決這個非同步問題的。
生產者消費者模式
public class App {
public static void main(String[] args) {
//共同的資源
Movie m=new Movie();
//多執行緒
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
/**
* 共同的資源
* 生產者消費者訊號燈法
* wait:等待,釋放鎖
* sleep:不釋放鎖
* notify()/notifyAll() 喚醒
*/
public class Movie {
private String pic;
//訊號燈
//flag==true,生產者生產,消費者等待,生產完成後通知消費者
//flag==false,消費者消費生產者等待,消費完成後通知生產
private boolean flag=true;
//播放
public synchronized void play(String pic){
if (!flag){//生產者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//開始生產
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生產了:"+pic);
//生產完畢
this.pic=pic;
//通知消費
this.notify();
//生產者停下
this.flag=false;
}
//檢視
public synchronized void watch(){
if (flag){//消費者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//開始消費
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消費了:"+pic);
//消費完畢
//通知生產
this.notifyAll();
//消費停止
this.flag=true;
}
}
/**
* 生產者
*/
public class Player implements Runnable {
private Movie movie;
public Player(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i=0;i<20;i++){
if (0==i%2){
movie.play("左青龍");
}else {
movie.play("右白虎");
}
}
}
}
/**
* 消費者
*/
public class Watcher implements Runnable {
private Movie movie;
public Watcher(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for (int i=0;i<20;i++){
movie.watch();
}
}
}
執行緒的任務排程:
Timer定時器類
TimerTask任務類
在Spring的任務排程就是通過這兩個類來實現的
在這種實現方式中,Timer類實現的是類似鬧鐘的功能,也就是定時或者每隔一定時間觸發一次執行緒。其實,Timer類本身實現的就是一個執行緒,只是這個執行緒是用來實現呼叫其他執行緒的。而TimerTask類是一個抽象類,該類實現了Runnable介面,所以該類具備多執行緒的能力。
在這種實現方式中,通過繼承TimerTask使該類獲得多執行緒的能力,將需要多執行緒執行的程式碼書寫在run方法內部,然後通過Timer類啟動執行緒的執行
在實際使用時,一個Timer可以啟動任意多個TimerTask實現多執行緒,但是多個執行緒之間會阻塞。所以如果多個執行緒之間如果需要完全獨立執行的化,最好還是一個Timer啟動一個TimerTask實現
import java.util.Timer;
import java.util.TimerTask;
public class schedual {
//delay:表示執行時間
//period:表示每隔多少秒執行一次
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("很容易....");
}
},2000,200);
}
}