天天看點

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

文章目錄

  • 概念
  • 線程建立
    • 繼承Thread類
    • 實作Runnable接口
    • 實作Callable接口
  • 補充知識點
    • 靜态代理模式
    • Lamda表達式
  • 線程狀态
    • 線程停止(stop)
    • 線程休眠(sleep)
    • 線程禮讓(yield)
    • 線程強制執行(join)
    • 線程優先級
    • 守護程序
  • 線程同步
    • 線程不安全舉例
      • 不安全買票
      • 不安全銀行
      • 不安全集合
    • 同步方法
      • 同步方法,同步塊(synchronized)
        • 安全買票代碼
        • 安全取錢代碼
        • 安全list代碼
    • JUC
  • 死鎖
  • Lock鎖
    • 不加鎖的情況
    • 加鎖的代碼
  • 線程協作
    • 生産者消費者問題
    • 管程法
    • 信号燈法
  • 線程池
    • 特點
  • 學習連結

概念

注意:很多多線程是模拟出來的,真正的多線程是指有多個cpu,即多核,如服務 器。如果是模拟出來的多線程,即在一個cpu的情況下,在同一個時間點,cpu隻能 執行一個代碼,因為切換的很快,是以就有同時執行的錯覺。

  • 線程就是獨立的執行路徑;
  • 在程式運作時,即使沒有自己建立線程,背景也會有多個線程,如主線程,gc線程;
  • main() 稱之為主線程,為系統的入口,用于執行整個程式;
  • 在一個程序中,如果開辟了多個線程,線程的運作由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能認為的幹預的。
  • 對同一份資源操作時,會存在資源搶奪的問題,需要加入并發控制;
  • 線程會帶來額外的開銷,如cpu排程時間,并發控制開銷。
  • 每個線程在自己的工作記憶體互動,記憶體控制不當會造成資料不一緻

線程建立

繼承Thread類

1.繼承Thread類

2.重寫run方法

3.建立對象調用star();

測試

package collectionstest; 
import java.lang.*; 
public class ThreadTest {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("1");
		MyThread mt2 = new MyThread("2222222222222222222222222222222222222222222222");
		MyThread mt3 = new MyThread("33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333");
		mt1.start();
		mt2.start();
		mt3.start();
		for(int i = 0;i<100;i++) {
			System.out.println("666666666666666666666");
		} 
	}
}

 

class MyThread extends Thread{
	private String k;
	MyThread(String n){
		this.k = n;
	}
	@Override
	public void run() { 
		for(int i = 0;i<100;i++) {
			System.out.println(k);
		}
	}
} 
           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

實作Runnable接口

1.實作Runnable接口

2.實作run方法

3.建立對象,調用start方法

測試:

package collectionstest;

public class RunnableTest {
	public static void main(String[] args) {
		
		MyRunnable mr =new MyRunnable("0");
		Thread myThread = new Thread(mr);
		myThread.start();
		
		MyRunnable mr1 =new MyRunnable("11111111");
		Thread myThread1 = new Thread(mr1);
		myThread1.start();
		
	}
}
class MyRunnable implements Runnable{
	private String k;
	MyRunnable(String n){
		this.k = n;
	}
	@Override
	public void run() { 
		for(int i = 0;i<100;i++) {
			System.out.println(k);
		}
	}
}  
           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

實作Callable接口

測試代碼

package collectionstest;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class myCallable implements Callable<Boolean>{
	private String msg;
	myCallable(String s){
		msg = s;
	}
	public Boolean call() {
		System.out.println(msg);
		return true;
	}
}
public class CallableTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//建立目标對象
		myCallable mc0 = new myCallable("1");
		myCallable mc1 = new myCallable("22");
		myCallable mc2 = new myCallable("333");
		//建立執行服務
		ExecutorService es = Executors.newFixedThreadPool(3);
		//送出執行
		Future<Boolean> f0 = es.submit(mc0);
		Future<Boolean> f1 = es.submit(mc1);
		Future<Boolean> f2 = es.submit(mc2);
		//擷取結果
		boolean r0 = f0.get();
		boolean r1 = f1.get();
		boolean r2 = f2.get(); 
		System.out.println(""+r0 + " " + r1 + " "+ r2);
		//關閉服務
		es.shutdownNow();
	} 
}

           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

補充知識點

靜态代理模式

也就是讓一個類幫助另一個類做事情(可以做一些額外的準備)。

代碼

interface Marry{
	void happyMarry(); 
}
class Human implements Marry{
	String name;
	Human(String s){
		name = s;
	}
	public void happyMarry() {
		System.out.println(name + "結婚了");
	}
}
class WeddingCompany implements Marry {
	private Human human;
	WeddingCompany(Human a){
		human = a;
	}
	private void before() {
		System.out.println("結婚前的準備");
	}
	private void after() {
		System.out.println("結婚後的整理");
	}
	public  void happyMarry() {
		before();
		human.happyMarry();
		after();
	}
}
public class staticPorxy {
	public static void main(String[] args) {
		WeddingCompany weddingCompany = new WeddingCompany(new Human("一個人"));  
		weddingCompany.happyMarry();
	}
} 
           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

Lamda表達式

隻用一次的類: 内部類 -> 匿名内部類 -> 局部内部類 -> 匿名内部類 -> lamda表達式。

特點:

避免匿名内部類定義過多

代碼很簡潔,隻留下核心的邏輯。

函數式接口:

隻有唯一一個抽象方法。可以通過lamda表達式來建立該接口的對象。

代碼

package ProcessThreadTest;
interface MyInterface{
	public void function();
}
interface MyInterface1{
	public void function(String a);
}
class ClassOne implements MyInterface{
	public void function() {
		System.out.println("普通類實作接口");
	} 
}
public class lamdaTest {
	//靜态内部類
	static class ClassTwo  implements MyInterface{
		public void function() {
			System.out.println("靜态内部類實作接口");
		}
	}
	public static void main(String[] args) {
		//普通方法調用不使用lamda表達式
		MyInterface myclass = new ClassOne();
		myclass.function();
		//靜态方法調用
		myclass = new ClassTwo();
		myclass .function();
		//局部内部類調用
		class ClassThree implements MyInterface{
			public void function() {
				System.out.println("局部内部類實作接口");
			}
		}
		myclass = new ClassThree();
		myclass.function();
		//匿名内部類
		myclass = new MyInterface() {
			public void function() {
				System.out.println("匿名内部類實作接口");
			}
		};
		myclass.function();
		//lamda表達式實作
		myclass = ()->{
			System.out.println("lamda表達式");
		};
		myclass.function();
		//帶參數
		MyInterface1 myclass1 = (String a)->{
			System.out.println("參數是:"+a);
		};
		myclass1.function("帶參數");
		//省略參數類型
		myclass1= myclass1 = (a)->{
			System.out.println("參數是:"+a);
		};
		myclass1.function("省略類型");
		//省略括号 
		myclass1= myclass1 = a->{
			System.out.println("參數是:"+a);
		};
		myclass1.function("省略括号");
		//省略花括号(隻有一行)
		myclass1= myclass1 = a -> System.out.println("參數是:"+a); 
		myclass1.function("省略花括号");
	}
}

           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程狀态

線程的幾種狀态:

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結
Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

Thread類的一些重要的方法:

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程停止(stop)

不建議使用stop方法和destory方法。

從Thread類的源碼中可以看到,這個stop方法已經廢棄。

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

建議線程通過循環次數自動停止下來(上文已實作)或者設定标志位。

标志位代碼

package ProcessThreadTest;
class MyTheadStop implements Runnable{//此處不能繼承Thread類,因為stop方法在Thread類中是
	boolean flag = true;
	public void run() {
		while(flag ==true) {
			System.out.println("hi!");
		}
	}
	public void stop() {
		this.flag = false;
	}
	
}
public class ThreadStopTest {
	public static void main(String[] args) {
		MyTheadStop  mts = new MyTheadStop();
		Thread myThread = new Thread(mts);
		myThread.start();
		for(int i = 0;i<300;i++) {
			if(i == 150) {
				mts.stop();
				System.out.println("線程停止了");
			}
			System.out.println(i);
		}
	}
}

           
Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

. . . . . . . . .

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程休眠(sleep)

sleep (時間)指定目前線程阻塞的毫秒數;

sleep存在異常InterruptedException;

sleep時間達到後線程進入就緒狀态;

sleep可以模拟網絡延時,倒計時等。

每一個對象都有一個鎖,sleep不會釋放鎖;

代碼

package ProcessThreadTest;

import java.sql.Time;
import java.util.Date;

public class SleepTest implements Runnable {
	private int ticketNums = 1;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			Date date = new Date() ;
			if(ticketNums > 10) {
				break;
			}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + date + ":拿到了第"+ ticketNums +"張票");
			ticketNums++;
		}
	}
	
	public static void main(String[] args) {
		SleepTest sleepTest = new SleepTest();
		new Thread(sleepTest,"小明").start();
		new Thread(sleepTest,"小紅").start();
		new Thread(sleepTest,"小亮").start();
	}
}

           

結果(讀資料之前沒有加鎖,暫不解決)

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程禮讓(yield)

禮讓線程,讓目前正在執行的線程暫停,但不阻塞。

将線程從運作狀态轉為就緒狀态,讓cpu重新排程,禮讓不一定成功!

測試代碼

package ProcessThreadTest;
class myYield implements Runnable{
	private String name;
	myYield(String a){
		name = a;
	}
	public void run() {
		System.out.println(name + "禮讓前");
		Thread.yield();
		System.out.println(name + "禮讓後");
	}
}
public class YieldTest {
	public static void main(String[] args) {
		new Thread( new myYield("小明") ).start();
		new Thread( new myYield("小紅") ).start(); 
	}
}

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程強制執行(join)

Join合并線程,待此線程執行完成後,再執行其他線程,其他線程阻塞 ,可以想象成插隊

測試代碼

package ProcessThreadTest;

public class JoinTest implements Runnable {
	public static void main(String[] args) throws InterruptedException {
		JoinTest jt = new JoinTest();
		Thread thread = new Thread(jt);
		thread.start();
		for(int i = 0;i<100;i++) {
			System.out.println("main:"+i);
			if(i==50) {
				thread.join();
			}
		}
	}

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

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程優先級

Java提供一個線程排程器來監控程式中啟動後進入就緒狀态的所有線程,線程排程 器按照優先級決定應該排程哪個線程來執行。線程的優先級用數字表示,範圍從1~10。

Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
           

getPriority()擷取優先級,setPriority(int xxx)設定優先級。

守護程序

特點

  • 線程分為使用者線程和守護線程
  • 虛拟機必須確定使用者線程執行完畢
  • 虛拟機不用等待守護線程執行完畢
  • 如,背景記錄記錄檔,監控記憶體,垃圾回收等待

    代碼

package ProcessThreadTest; 
class DeamonClass implements Runnable{ 
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true)
			System.out.println("守護程序--------------");
	} 
}

class NormalClass implements Runnable{ 
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0;i<100;i++) {
			System.out.println("普通程序");
		}
		System.out.println("普通程序結束~~~~~");
	} 
}
public class DeamonTest {
	public static void main(String[] args) {  
		Thread thread = new Thread(new DeamonClass()); 
		thread.setDaemon(true);
		thread.start();
		new Thread(new NormalClass()).start(); 
	} 
}

           

運作結果:

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程同步

處理多線程問題時 , 多個線程通路同一個對象 , 并且某些線程還想修改這個對象 。這時候我們就需要線程同步 。線程同步其實就是一種等待機制 , 多個需要同時通路此對象的線程進入這個對象的等待池形成隊列, 等待前面線程使用完畢 , 下一個線程再使用。

由于同一程序的多個線程共享同一塊存儲空間 , 在帶來友善的同時,也帶來了通路 沖突問題 , 為了保證資料在方法中被通路時的正确性 , 在通路時加入 鎖機制synchronized , 當一個線程獲得對象的排它鎖 , 獨占資源 , 其他線程必須等待 , 使用後釋放鎖即可 . 存在以下問題 :

一個線程持有鎖會導緻其他所有需要此鎖的線程挂起 ;

在多線程競争下 , 加鎖 , 釋放鎖會導緻比較多的上下文切換 和 排程延時,引起性能問題 ;

如果一個優先級高的線程等待一個優先級低的線程釋放鎖 會導緻優先級倒置 , 引起性能問題 。

線程不安全舉例

不安全買票

代碼

package ProcessThreadTest;

class BuyTicket implements Runnable{
	private int ticketNums = 10;
	boolean flag = true;
	public void buy() throws InterruptedException {
		if(ticketNums<=0) {
			flag = false;
			return;
		}
		Thread.sleep(100); //模拟網絡延遲
		System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
	}
	public void run() {
		while(flag) {
			try {
				buy();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
public class UnsafeBuyTIcket {
	public static void main(String[] args) {
		BuyTicket bt = new BuyTicket();
		new Thread(bt,"A").start();
		new Thread(bt,"B").start();
		new Thread(bt,"C").start();
	}
}

           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

不安全銀行

代碼

package ProcessThreadTest;

public class UnsafeBank {
	public static void main(String[] args) {
		Account account = new Account(100,"零花錢");
		Drawing A = new Drawing(account,50,"A");
		Drawing B = new Drawing(account,100,"B");
		A.start();
		B.start();
	}
}
class Account{
	int money;
	String name;
	Account(int a,String b){
		money = a; 
		name = b;
	}
}


class Drawing extends Thread{
	Account account;
	int drawingMoney;
	String name;
	public Drawing(Account account,int drawingMoney,String name) {
		this.account = account;
		this.drawingMoney = drawingMoney;
		this.name = name;
	}
	
	public void run() {
		if(account.money < drawingMoney) {
			System.out.println(Thread.currentThread().getName() + "錢不夠");
			return;
		} 
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		account.money -= drawingMoney; 
		System.out.println(name + "取了:"+ drawingMoney);
		System.out.println(account.name + "餘額為" + account.money);
	}
}
           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

不安全集合

代碼

package ProcessThreadTest;

import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		for(int i = 0;i<10000;i++) {
			new Thread( ()->{
				list.add(Thread.currentThread().getName());
			}).start();
		}
		System.out.println(list.size());
	}
}

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

可以看到list中的元素并不是一萬個,因為有的是同時新增的,其中的某些就被覆寫掉了。

同步方法

由于我們可以通過 private 關鍵字來保證資料對象隻能被方法通路 , 是以我們隻需 要針對方法提出一套機制 , 這套機制就是 synchronized 關鍵字 , 它包括兩種用法 : synchronized 方法 和synchronized 塊。

同步方法 : public synchronized void method(int args) {}

synchronized方法控制對 “對象” 的通路 , 每個對象對應一把鎖 , 每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行 , 否則線程會阻塞 , 方法一旦執行 , 就獨占該鎖 , 直到該方法傳回才釋放鎖 , 後面被阻塞的線程才能獲得這個鎖 , 繼續執行。

缺陷 : 若将一個大的方法申明為synchronized 将會影響效率

同步方法,同步塊(synchronized)

Obj 稱之為同步螢幕

Obj 可以是任何對象 , 但是推薦使用共享資源作為同步螢幕

同步方法中無需指定同步螢幕 , 因為同步方法的同步螢幕就是this , 就是 這個對象本身 , 或者是 class

同步螢幕的執行過程

  1. 第一個線程通路 , 鎖定同步螢幕 , 執行其中代碼 。
  2. 第二個線程通路 , 發現同步螢幕被鎖定 , 無法通路。
  3. 第一個線程通路完畢 , 解鎖同步螢幕 。
  4. 第二個線程通路, 發現同步螢幕沒有鎖 , 然後鎖定并通路。

安全買票代碼

隻要把之前不安全的買票的buy方法加上synchronized修飾就可以保證同步了。

1.方法一
	public synchronized void buy() throws InterruptedException {
		if(ticketNums<=0) {
			flag = false;
			return;
		}
		Thread.sleep(100);
		System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
	} 

           
Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

安全取錢代碼

synchronized 預設鎖的是this方法,但這應該鎖Account類而不是Drawing類。

可以把幾個函數封裝在synchronized之中。

public void run() {
		//account不是本對象,不能用synchronized方法。
		synchronized(account) {
			if(account.money < drawingMoney) {
				System.out.println(account.name  + " 錢不夠");
				return;
			} 
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			account.money -= drawingMoney; 
			System.out.println(name + "取了:"+ drawingMoney);
			System.out.println(account.name + "餘額為" + account.money);
		}
		
	}
           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

安全list代碼

package ProcessThreadTest;

import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
	public static void main(String[] args) throws InterruptedException {
		List<String> list = new ArrayList<String>();
		for(int i = 0;i<10000;i++) {
			new Thread( ()->{ 
				synchronized(list) {
					list.add(Thread.currentThread().getName());
				} 
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(list.size());
	}
}

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

JUC

安全類型的集合。

代碼

package ProcessThreadTest;

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListTest {
	public static void main(String[] args) throws InterruptedException {
		CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
		for(int i = 0;i<10000;i++) {
			new Thread( ()->{
				list.add(Thread.currentThread().getName());
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(list.size());
	}
}

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

死鎖

産生原因

多個線程各自占有一些共享資源 , 并且互相等待其他線程占有的資源才能運作 , 而導緻兩個或者多個線程都在等待對方釋放資源 , 都停止執行的情形 。某一個同步塊同時擁有 “ 兩個以上對象的鎖 ” 時 , 就可能會發生 “ 死鎖 ” 的問題。

解決方法:破壞任意條件

1. 互斥條件:一個資源每次隻能被一個程序使用。 
2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。 
3. 不剝奪條件 : 程序已獲得的資源,在末使用完之前,不能強行剝奪。 
4. 循環等待條件 : 若幹程序之間形成一種頭尾相接的循環等待資源關系。
           

實驗代碼

package ProcessThreadTest;

public class DeadLockTest {
	public static void main(String[] args) {
		Makeup q1 = new Makeup(0,"A");
		Makeup q2 = new Makeup(1,"B");
		q1.start();
		q2.start();
	}
}
class Lipstick{
	
}
class Mirror{
	
}

class Makeup extends Thread{
	static Lipstick lipstick = new Lipstick();
	static Mirror mirror = new Mirror();
	int choice;
	String girlName;
	Makeup(int Choice,String girlName){
		this.choice = Choice;
		this.girlName = girlName;
	}
	public void run() {
		try {
			makeup();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	private void makeup() throws InterruptedException {
		if(choice == 0) {
			synchronized(lipstick) {
				System.out.println(this.girlName + "獲得口紅鎖");
				Thread.sleep(1000);
				synchronized(mirror) {
					System.out.println(this.girlName + "獲得鏡子鎖");
				}
			}
			
		}else {
			synchronized(mirror) {
				System.out.println(this.girlName + "獲得鏡子鎖");
				Thread.sleep(1000);
				synchronized(lipstick) {
					System.out.println(this.girlName + "獲得口紅鎖");
				}
			}
			
		}
	}
}
           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

Lock鎖

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

從JDK 5.0開始,Java提供了更強大的線程同步機制–通過顯式定義同步鎖對象來實作同步。同步鎖使用Lock對象充當java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行通路的工具。鎖提供了對共享資源的獨占通路,每次隻能有一個線程對Lock對象加鎖,線程開始通路共享資源之前應先獲得Lock對象 ReentrantLock類實作了Lock ,它擁有與synchronized相同的并發性和記憶體語義,在實作線程安全的控制中,比較常用的ReentrantL ock,可以顯式加鎖、釋放鎖。

synchronized 與 Lock 的對比

  • Lock是顯式鎖(手動開啟和關閉鎖,别忘記關閉鎖)
  • synchronized是隐式鎖,出了作用域自動釋放
  • Lock隻有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
  • 使用Lock鎖,JVM将花費較少的時間來排程線程,性能更好。并且具有更好的擴充性(提供更多的子類)
  • 優先使用順序: Lock > 同步代碼塊(已經進入了方法體,配置設定了相應資源)> 同步方法(在方法體之外)

不加鎖的情況

代碼

package ProcessThreadTest;
class MyLock implements Runnable{
	int n = 10;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			if(n > 0) {
				try {
					Thread.sleep(1000);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(n--);
			}else {
				break;
			}
		}
	}
	
}
public class LockTest {
	public static void main(String[] args) {
		MyLock myLock = new MyLock();
		new Thread(myLock).start();
		new Thread(myLock).start();
		new Thread(myLock).start();
	}
}

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

加鎖的代碼

代碼

class MyLock implements Runnable{
	int n = 10;
	private final ReentrantLock lock = new ReentrantLock(); 
	@Override
	public void run() { 
		while(true) {
			try{
				lock.lock();
				if(n > 0) {
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(n--);
				}else {
					break;
				}
			}finally {
				lock.unlock();
			}
			
		}
	}
	
} 

           

結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程協作

生産者消費者問題

假設倉庫中隻能存放一件産品 , 生産者将生産出來的産品放入倉庫 , 消費者将倉庫中産品取走消費 。

如果倉庫中沒有産品 , 則生産者将産品放入倉庫 , 否則停止生産并等待 , 直到 倉庫中的産品被消費者取走為止 。

如果倉庫中放有産品 , 則消費者可以将産品取走消費 , 否則停止消費并等待 , 直到倉庫中再次放入産品為止 。

這是一個線程同步問題 , 生産者和消費者共享同一個資源 , 并且生産者和消費者之間互相依賴 , 互為條件。對于生産者 , 沒有生産産品之前 , 要通知消費者等待 . 而生産了産品之後 , 又 需要馬上通知消費者消費。對于消費者 , 在消費之後 , 要通知生産者已經結束消費 , 需要生産新的産品以供消費. 在生産者消費者問題中 , 僅有synchronized是不夠的。synchronized 可阻止并發更新同一個共享資源 , 實作了同步 u synchronized 不能用來實作不同線程之間的消息傳遞 (通信)。

解決線程通信的方法

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

注意 : 均是Object類的方法 , 都隻能在同步方法或者同步代碼塊中使用,否則會抛出異常IllegalMonitorStateException

管程法

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

代碼實作

package ProcessThreadTest;

import java.util.Random;

public class ProductorConsumerTest {
	public static void main(String[] args) {
		SynContainer sc = new SynContainer();
		new Productor(sc).start();
		new Consumer(sc).start();
	}
}
class toy{
	private int id;
	toy(int i){
		setId(i);
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
}
class Productor extends Thread{
	SynContainer sc = new SynContainer();
	Productor(SynContainer sc){
		this.sc = sc;
	}
	public void run() {
		Random random = new Random();
		for(int i = 1;i<=100;i++) {
			try {
				Thread.sleep(random.nextInt(80));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			this.sc.push(new toy(i));
			System.out.println("生産了第"+i+"隻雞"+",剩餘"+sc.getNums());
		}
	}
}

class Consumer extends Thread{
	SynContainer sc = new SynContainer();
	Consumer(SynContainer sc){
		this.sc = sc;
	}
	public void run() {
		Random random = new Random();
		for(int i = 0;i<100;i++) {
			try {
				Thread.sleep(random.nextInt(100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			toy t = this.sc.pop();
			System.out.println("消費了第" +t.getId()+"隻雞"+",剩餘"+sc.getNums());
		}
	}
}
class SynContainer{
	private toy[] toys = new toy[10];
	private int nums = 0; 
	public int getNums() {
		return nums;
	}
	public synchronized void push(toy t) { 
		if(nums >= 10) { 
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
		}
		toys[nums] = t;
		nums++; 
		this.notifyAll();
	}
	public synchronized toy pop() {
		if(nums <= 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		nums--;
		toy t = toys[nums];
		this.notifyAll();
		return t;
	}
}
           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

信号燈法

代碼測試

package ProcessThreadTest;

public class ProductorConsumerTest1 {
	public static void main(String[] args) {
		TV tv = new TV();
		new Watcher(tv).start();
		new Player(tv).start();
	}
}
class TV{
	String program;
	boolean flag = true;//true表示開始未表演狀态 
	public synchronized void play(String program) {
		if(!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("演員表演了:"+program);
		this.notifyAll(); //通知觀衆來看
		this.program = program;
		this.flag = !this.flag;
	}
	
	public synchronized void watch() {
		if(flag) {
			try {
				this.wait();
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("觀看了"+program);
		this.notifyAll();
		this.flag = !this.flag;
	}
}
class Player extends Thread{
	TV tv;
	public Player(TV tv) {
		this.tv = tv;
	}
	public void run() {
		for(int i = 0;i<10;i++) { 
			this.tv.play("節目"+i); 
		}
	}
}
class Watcher extends Thread{
	TV tv;
	public Watcher(TV tv) {
		this.tv = tv;
	}
	public void run() {
		for(int i = 0;i<10;i++) { 
			this.tv.watch(); 
		}
	}
}
           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

線程池

特點

背景:經常建立和銷毀、使用量特别大的資源,比如并發情況下的線程,對性能影響很大。 思路:提前建立好多個線程,放入線程池中,使用時直接擷取,使用完放回池中。 可以避免頻繁建立銷毀、實作重複利用。

好處: 提高響應速度;降低資源消耗;便于線程管理。

corePoolSize:核心池的大小 
maximumPoolSize:最大線程數
keepAliveTime:線程沒有任務時最多保持多長時間後會終止
           

JDK 5.0起提供了線程池相關API:

ExecutorService

Executors

ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor 
void execute(Runnable command) :執行任務/指令,沒有傳回值,一般用來執行Runnable <T> Future<T> submit(Callable<T> task):執行任務,有傳回值,一般又來執行 Callable
void shutdown() :關閉連接配接池
Executors:工具類、線程池的工廠類,用于建立并傳回不同類型的線程池
           

測試代碼

package ProcessThreadTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {
	public static void main(String[] args) {
		ExecutorService service = Executors.newFixedThreadPool(10);
		
		service.execute(new MyThread00());
		service.execute(new MyThread00());
		service.execute(new MyThread00());
		service.execute(new MyThread00());
		service.execute(new MyThread00());
		service.execute(new MyThread00());
		
		service.shutdown();
	}
}

class MyThread00 implements Runnable{

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

           

運作結果

Java學習七-多線程概念線程建立補充知識點線程狀态線程同步死鎖Lock鎖線程協作線程池學習連結

學習連結

https://kuangstudy.com/course