Java基礎 線程入門

Java基礎 線程

線程的概述

進程 正在運行的程序 負責這個程序的內存空間分配 代表內存中的執行區域

線程 就是在一個進程中負責一個執行路徑

多線程 就是在一個進程中多個路徑同時執行(例如殺毒軟件同時在做系統優化和垃圾清理)

自定義線程方式(一)

一種方式是將該類聲明爲Thread子類

(1) 該子類重寫Thread的run方法 將自定義線程的任務代碼放到run方法中

(2) 創建自定義線程對象 調用start方法 線程開啓後調用到run方法

(不能直接調用run方法 只有調用start方法纔算開啓一個線程

public class Demo extends Thread {
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println("自定義線程"+i);
		}
	}
	public static void main(String[] args) {
		Demo d=new Demo();
		d.start();
		for(int i=0;i<100;i++) {
			System.out.println("main線程"+i);
		}
	}
}

在這裏插入圖片描述

結果可以看到交替出現

自定義線程方式(二)

自定義一個類去實現Runnable接口

(1)自定義一個類實現Runnable接口

(2)實現Runnable接口的run方法 把自定義線程的任務代碼定義在run方法上

(3)創建Runnable實現類對象

(4)創建Thread對象 把Runnable實現類對象作爲參數傳入

(5)調用Thread的start方法開啓線程

public class Demo implements Runnable {
	public static void main(String[] args) {
		Demo d=new Demo();
		Thread t=new Thread(d,"Mythread");
		t.start();
		for(int i=0;i<100;i++) {
			System.out.println("main線程"+i);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
}

在這裏插入圖片描述
同樣的我們看到結果的交替出現

這是我們來看一個問題 爲什麼要把Runnable實現類對象作爲參數傳遞進去呢?

我們看兩段Thread源碼中的操作

//這個構造方法中 用target變量記錄了Runnable實現類對象
public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
/*這個是Thread類的run方法 可以看到在Thread的run方法中 又調用了
tatget的run方法 這個run方法就是我們自己重寫的run方法*/
public void run() {
        if (target != null) {
            target.run();
        }
    }

這樣我們在加深一下理解

public class Demo implements Runnable {
	public static void main(String[] args) {
		Demo d=new Demo();
		Thread t=new Thread(d,"Mythread");
		t.start();
		for(int i=0;i<100;i++) {
			System.out.println("main線程"+i);
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
		System.out.println(Thread.currentThread());//當前線程對象 t
		System.out.println(this);//該函數的調用者對象 d
	}
}

在這裏插入圖片描述

this關鍵字指的是該函數的調用者對象 這個run方法目前是由target在調用 而恰恰target就是記錄的Runnable實現類的對象 也就是d對象

線程生命週期

(害 本來想自己畫圖來 可是這個圖太漂亮了 就直接拿過來了)

在這裏插入圖片描述

線程安全問題

我們來模擬一個線程安全問題

現在有這樣一個需求 3個窗口要賣50張票 我們將3個窗口看做3個線程 來模擬實現這個問題

class TicketSales extends Thread{
	static int num=50;
	public  TicketSales(String name) {
		super(name);
	}
	@Override
	public void run() {
		while(true) {
			if(num>0) {
				System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
				num--;
			}else {
				System.out.println("售完了");
				break;
			}
		}
	}
}
public class Demo {
	public static void main(String[] args) {
		TicketSales thread1=new TicketSales("窗口1");
		TicketSales thread2=new TicketSales("窗口2");
		TicketSales thread3=new TicketSales("窗口3");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

這時候的結果 我們來看 出現了線程安全問題 50號票被賣了3次

在這裏插入圖片描述

我們來分析一下這個原因 可能的情況是當thread1剛剛執行到買票這句話是 還沒來得及做num–時 這時候的cpu執行權被thread3 搶走了 同樣的當thread3執行到買票這句話時 cpu的執行權又被thread2搶走了 這就造成了50號票被賣了3次的情況

同步代碼塊解決線程安全問題

class TicketSales extends Thread{
	static int num=50;
    //鎖對象必須是多個線程共享的對象 否則沒有意義 用static修飾
	static Object o=new Object();
	public  TicketSales(String name) {
		super(name);
	}
	@Override
	public void run() {
		while(true) {
			synchronized (o) {//o是多個線程共享的鎖對象
				if(num>0) {
					System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
					num--;
				}else {
					System.out.println("售完了");
					break;
				}
			}
		}
	}
}
public class Demo {
	public static void main(String[] args) {
		TicketSales thread1=new TicketSales("窗口1");
		TicketSales thread2=new TicketSales("窗口2");
		TicketSales thread3=new TicketSales("窗口3");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

在這裏插入圖片描述

這時 利用同步代碼塊解決了線程安全問題

Lock鎖與Synchronized的區別(附加內容)

這篇博客 寫到這裏 又去b戰上搜了一下juc 於是又發現了新大陸 關於線程8鎖 以後一定會加強自己的學習 這裏先簡單記錄一下自己這個小的學習過程

(這裏簡單升級了一下代碼 現學現賣 害)

我們來對比一下lock鎖與synchronized的代碼的不同寫法以及區別

Synchronized方式

public class Demo {
	public static void main(String[] args) {	
		Ticket ticket=new Ticket();
        //lambda表達式 (參數)->{業務代碼}
        //將資源類丟入到線程當中 這裏大家可以去csdn搜一下lambda創建線程和傳統方式創建線程
		new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
		new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
		new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
	}
}
//資源類
class Ticket{
	private static int num=50;

	public synchronized void sale() {
		if(num>0) {
			System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
			num--;
		}
	}
}

Lock鎖方式

public class Demo {
	public static void main(String[] args) {	
		Ticket ticket=new Ticket();
		new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
		new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
		new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
	}
}
//資源類
class Ticket{
	private static int num=50;
	
	Lock lock=new ReentrantLock();
	public  void sale() {
		lock.lock();//加鎖
		try {
			if(num>0) {
				System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
				num--;
			}
		} catch (Exception e) {
			
		}finally {
			lock.unlock();
		}
	}
}

Synchronized和Lock的區別

Synchronized 是內置的Java 關鍵字 Lock是Java類

Synchronized 無法判斷鎖的狀態 Lock可以判斷是否獲取到了鎖

Synchronized 會自動釋放鎖 Lock要手動釋放鎖

Synchronized 線程1(獲得鎖 阻塞) 線程2(等待 傻傻的等 ) Lock鎖就不一定會等下去(會嘗試獲取鎖)

Synchronized 可重入鎖 不可中斷 非公平鎖 Lock 可重入鎖 可以中斷 默認非公平鎖(可以自己設置)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章