Java—多執行緒和併發程式設計

2020-08-13 10:05:04

1.執行緒知識回顧

1.1 程式、進程、執行緒

程式:完成特定功能的一系列有序指令的集合
一個可執行檔案
程式碼段+數據段

進程:程式的一次動態執行過程
程式碼段+數據段+堆疊段+PCB

一個進程只對應一個程式
一個程式可以有很多進程

執行緒:程式的一個可執行路線
進程是資源競爭的基本單位
執行緒是程式執行的最小單位
執行緒共用進程數據,例如進程各種ID,地址空間,信號處理器表格,檔案描述符表
但也擁有自己的一部分數據(CPU狀態),執行緒ID,一組暫存器(堆疊地址指針),棧,errno,信號狀態,優先順序

1.2在Java中建立執行緒的兩種方式

1)繼承THread
2)實現Runnable介面

1.3執行緒的生命週期

在这里插入图片描述

1.4同步與死鎖

1)同步方法 同步監視器爲當前物件this
2)同步程式碼快(同步監視器/共用資源的物件){
}
3)過多的同步就會導致死鎖

1.5執行緒間的通訊

  1. wait()
  2. notify()
  3. notifyAll()

1.6案例

編寫兩個執行緒,一個執行緒列印1-52的整數,另一個執行緒列印字母A-Z。列印順序爲12A34B56C…5152Z。即按照整數和字母的順序從小到大列印,並且每列印兩個整數後,列印一個字母,交替回圈列印,直到列印到整數52和字母Z結束。
要求:
1)編寫列印類Printer,宣告私有屬性index,初始值爲1,用來表示是第幾次列印。
2)在列印類Printer中編寫列印數位的方法print(int i),3的倍數就使用wait()方法等待,否則就輸出 i ,使用notifyAll()進行喚醒其他執行緒。
3)在列印類Printer中編寫列印字母的方法print(char c),不是3的倍數就等待,否則就列印輸出字母c,使用notifyAll()進行喚醒其他執行緒。
4)編寫列印數位的執行緒 NumberPrinter 繼承 Thread 類,宣告
私有屬性 private Printer p;在構造方法中進行賦值,實現父類別
的 run 方法,呼叫 Printer 類中的輸出數位的方法。
5) 編寫列印字母的執行緒 LetterPrinter 繼承 Thread 類,宣告私
有屬性 private Printer p;在構造方法中進行賦值,實現父類別的
run 方法,呼叫 Printer 類中的輸出字母的方法。
6) 編寫測試類 Test,建立列印類物件,建立兩個執行緒類物件,
啓動執行緒。

checkPointThread

LetterPrinter.java

package com.bjsxt.thread;

public class LetterPrinter implements Runnable{
	private Printer printer;
	public LetterPrinter(Printer printer) {
		this.printer=printer;
	}
	@Override
	public void run() {
		for(char c='A';c<='Z';c++){
			printer.print(c);
		}
		
	}

}

NumberPrinter.java

package com.bjsxt.thread;

public class NumberPrinter implements Runnable {
	private Printer printer;
	public NumberPrinter(Printer printer) {
		this.printer=printer;
	}

	@Override
	public void run() {
		for(int i=1;i<=52;i++){
			printer.print(i);
		}
		
	}

}

Printer.java

package com.bjsxt.thread;

public class Printer {
	private int index=1;//用於統計第幾次列印
	public synchronized void print(int number){
		while(index%3==0){
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.print(number);
		index++;
		super.notifyAll();//喚醒在Printer這個物件上的所有的等待的執行緒
	}
	public synchronized void print(char letter){
		while(index%3!=0){
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.print(""+letter);
		index++;
		super.notifyAll();
	}
}

Test.java

package com.bjsxt.thread;

public class Test {
	public static void main(String[] args) {
		//(1)建立共用資源的物件
		Printer p=new Printer();
		NumberPrinter np=new NumberPrinter(p);
		LetterPrinter lp=new LetterPrinter(p);
		//建立代理類,並啓執行緒
		new Thread(np).start();
		new Thread(lp).start();
	}
}

2.實現執行緒的第三種方式

2.1實現執行緒前兩種的方式的缺點

1)沒有返回值
2)不支援泛型
3)異常必須處理

2.2實現多執行緒的第三種方式

實現Callable介面,重寫call方法

Callable功能更加強大
1)Future介面位於 java.util.concurrent 包中,可以對具體
Runnable、Callable任務的執行結果進行取消(cancel方法,嘗試取消執行任務)、查詢是否完成(isDone 方法)、獲取結果(get方法,等待完成,然後檢索其結果)等。
2) FutrueTask 是 Futrue 介面的唯一的實現類
3) FutureTask 同時實現了 Runnable, Future 介面。它既可以
作爲 Runnable 被執行緒執行,又可以作爲 Future 得到
Callable 的返回值
在这里插入图片描述
callableProject2

MyCallable.java

package com.bjsxt.callable;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {

	@Override
	public String call() throws Exception {
		String [] str={"apple","banana","orange","pear","grape"};
		int index=(int)(Math.random()*5);
		return str[index];
	}

}

Test.java

package com.bjsxt.callable;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//(1)建立任務
		MyCallable call=new MyCallable();
		//(2)交能任務管理 
		/**工作管理員是一個實現類,實現了RunnableFutrue介面,
		 * RunnableFutrue是Futrue與Runnable介面的子介面*/
		FutureTask<String> task=new FutureTask<>(call); //可以看成是Runnable介面的實現類
		//建立代理類並啓動執行緒
		Thread t=new Thread(task);
		t.start();
		System.out.println("獲取結果:"+task.get());
		//判斷任務是否執行完成
		System.out.println("任務是否執行完成:"+task.isDone());
	}
}

3.執行緒同步Lock鎖

3.1執行緒同步的第三種方法

Lock鎖:對需要上鎖的地方上鎖
1)JDK1.5後新增的功能
2)與Synchronized相比,Lock可提供多種鎖方案,更靈活
3) Java.util.concurrent.locks 中的 Lock 是一個介面,它的實現類是一個Java類,而不是作爲語言的特性(關鍵字)來實現
注意:如果同步程式碼有異常,要將unLock()放到finally中

3.2步驟

1)建立 Lock物件
2)呼叫 lock()方法上鎖
3)呼叫 unlock()方法解鎖

3.3Lock 與 與 synchronized 的區別

  1. Lock 是 顯 示 鎖 ( 手 動 開 啓 和 關 閉 鎖 , 別 忘 關 閉
    鎖),synchronized 是隱式鎖
  2. Lock 只有程式碼塊鎖,synchronized 有程式碼塊鎖和方法鎖
  3. 使用 Lock 鎖,JVM 將花費較少的時間來排程執行緒,效能更好,並且具有更好的擴充套件性(提供更多的子類)
    4)Lock確保當一個執行緒位於程式碼的臨界區時,另一個執行緒不進入臨界區。如果其他執行緒試圖進入鎖定的程式碼,則它將一直等待(即被阻止),直到該物件被釋放。Lock()方法會對Lock範例物件進行加鎖,因此所有對該物件呼叫Lock()方法的執行緒都會被阻塞,直到該Lock物件的unlock()方法被呼叫。

lockProject

CountRunnable.java

package com.bjsxt.account;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CountRunnable implements Runnable {
	private int count=0;//預設值
	//建立一個Lock物件
	Lock lock=new ReentrantLock();
	@Override
	public void run() {
		for(int i=0;i<10;i++){
			//synchronized (this) {
			try{
				lock.lock();//加鎖
				count++;
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"執行操作:count="+count);
				
			}finally{
				//解鎖
				lock.unlock();
			}
			}
			//}
	}

}

Test.java

package com.bjsxt.account;

public class Test {
	public static void main(String[] args) {
		CountRunnable cr=new CountRunnable();
		//代理類的物件
		Thread t1=new Thread(cr,"A");
		Thread t2=new Thread(cr,"B");
		Thread t3=new Thread(cr,"C");
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

4.執行緒池

4.1什麼是執行緒池

建立和銷燬物件是非常耗費時間的

建立物件:需要分配記憶體等資源

銷燬物件:雖然並不需要程式設計師操心,但是垃圾回收器會在後台一直跟蹤並銷燬
對於經常建立和銷燬、使用量特別大的資源,比如併發情況下的執行緒,對效能影響很大。

思路:建立好多個執行緒,放入執行緒池中,使用時直接獲取參照,不使用時放回池中。可以避免頻繁建立銷燬、實現重複利用。

生活案例:共用單車
技術案例:執行緒池、數據庫連線池
JDK1.5 起,提供了內建執行緒池

4.2執行緒池的好處

1)提高響應速度(減少了建立新執行緒的時間)
2)降低資源消耗(重複利用執行緒池中執行緒,不需要每次都建立)
3)提高執行緒的可管理性:避免執行緒無限制建立、從而銷燬系統資源,降低系統穩定性,甚至記憶體溢位或者CPU耗盡。

4.3執行緒池的應用場合

1)需要大量執行緒,並且完成任務的時間短
2)對效能要苛刻
3)接受突發性的大量請求

4.4 使用執行緒池執行大量的 Runnable 命令

4.5 使用執行緒池執行大量的 Callable 任務

threadPoolProject

Test1.java

package com.bjsxt.pool;

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

public class Test1 {
	public static void main(String[] args) {
		//如何建立一個執行緒池
		//(1)建立一個執行緒池,執行緒池中只有一個執行緒物件
		//ExecutorService pool1=Executors.newSingleThreadExecutor();
		//(2)建立一個執行緒池,執行緒池中有執行緒的數量固定
		//ExecutorService pool1=Executors.newFixedThreadPool(10);
		//(3)建立一個執行緒池,執行緒池中的執行緒的數量可以動態的改變
		ExecutorService pool1=Executors.newCachedThreadPool();
		/**使用執行緒池執行大量的Runnable命令*/
		for(int i=0;i<20;i++){
			final int n=i;
			//使用匿名內部類//任務
			Runnable command=new Runnable() {
				
				@Override
				public void run() {
					System.out.println("開始執行:"+n);
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("執行結束:"+n);
				}
			};//任務結束
			//將任務交給執行緒池中的執行緒去執行
			pool1.execute(command);
		}
		//關閉執行緒池
		pool1.shutdown();
	}
}

Test2.java

package com.bjsxt.pool;

import java.util.ArrayList;
import java.util.List;
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;

public class Test2 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 如何建立一個執行緒池
		// (1)建立一個執行緒池,執行緒池中只有一個執行緒物件
		 //ExecutorService pool1=Executors.newSingleThreadExecutor();
		// (2)建立一個執行緒池,執行緒池中有執行緒的數量固定
		 ExecutorService pool1=Executors.newFixedThreadPool(10);
		// (3)建立一個執行緒池,執行緒池中的執行緒的數量可以動態的改變
		//ExecutorService pool1 = Executors.newCachedThreadPool();
		//建立一個集合
		 List<Future> list=new ArrayList<Future>();
		/**使用執行緒池執行大量的Callable任務*/
		for(int i=0;i<20;i++){
			//使用匿名內部類
			//建立任務
			Callable<Integer> task=new Callable<Integer>() {

				@Override
				public Integer call() throws Exception {
					Thread.sleep(2000);
					return (int)(Math.random()*10)+1;
				}
				
			}; //任務結束
			//將任務交能執行緒池
			Future f=pool1.submit(task);
			list.add(f);
			//System.out.println(f.get());
		}
		System.out.println("ok?");
		//遍歷集合
		 for(Future ff:list){
			 System.out.println(ff.get());
		 }
		 System.out.println("OK!");
		//關閉執行緒池
		pool1.shutdown();
	}
}

5. 任務排程 Task_Timer

5.1 什麼是排程

任務:就是事情
排程:在不同的時間點或者在指定的時間點或者間隔多長時
間我去執行這個任務。
就是生活中的鬧鐘

5.2 相關的類

Timer 類:位於 java.util 包中

5.3 案例

實現時間的動態重新整理

TimerProject

Clock.java

package com.bjsxt.timer;

import java.util.Date;
import java.util.TimerTask;
/**
 * 任務
 * @author Administrator
 *
 */
public class Clock extends TimerTask{
	long time=1000;//1秒
	@Override
	public void run() {
		Date date=new Date(time);
		System.out.println(date.toLocaleString());
		time+=1000;
	}

}

TestTimer.java

package com.bjsxt.timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {
	public static void main(String[] args) {
		//(1)建立Timer的物件
		Timer t=new Timer();
		//(2)呼叫schedule()方法去執行任務
		//建立任務類的物件
		TimerTask task=new Clock();
		           //要執行的任務,第二個參數是任務的執行開始時間 ,第三個參數是每隔多長時間執行一次
		t.schedule(task, new Date(System.currentTimeMillis()+1000), 1000);
		
	}
}

6.ThreadLocal 是什麼_JDK 底層原理

6.1ThreadLocal

ThreadLocal 直譯爲「本地執行緒」,其實它就是一容器,用於
存放執行緒的區域性變數
作用:爲解決多執行緒程式的併發問題

6.2 案例

實現一個序列號的生成器程式

6.3ThreadLocal 的 的 API

在这里插入图片描述
threadLocalProject

MyThread.java

package com.bjsxt.test;

public class MyThread implements Runnable {
	private Sequence seq;
	public MyThread(Sequence seq) {
		this.seq=seq;
	}
	@Override
	public void run() {
		for(int i=0;i<3;i++){
			System.out.println(Thread.currentThread().getName()+"-->number"+seq.getNumber());
		}
		
	}

}

Sequence.java

package com.bjsxt.test;

public interface Sequence {
	public int getNumber();//每次呼叫時獲得一個數,下次呼叫時,這個數自增
}

SequenceImple.java

package com.bjsxt.test;

public class SequenceImple implements Sequence {
	private static int number=0;
	@Override
	public int getNumber() {
		number++;
		return number;
	}

}

SequenceImple2.java

package com.bjsxt.test;

public class SequenceImple2 implements Sequence {
	private static ThreadLocal<Integer> numberContainer=new ThreadLocal<Integer>(){
		protected Integer initialValue() {
			return 0;
		}
	};

	@Override
	public int getNumber() {
		numberContainer.set(numberContainer.get()+1);
		return numberContainer.get();
	}
	
}

Test.java

package com.bjsxt.test;

public class Test {
	public static void main(String[] args) {
		//(1)建立共用資源的物件
		//Sequence seq=new SequenceImple();
		Sequence seq=new SequenceImple2();
		//(2)建立執行緒類的物件
		MyThread m=new MyThread(seq);
		
		//建立三個代理,並啓動執行緒
		new Thread(m,"A").start();
		new Thread(m,"B").start();
		new Thread(m,"C").start();
	}
}

7.ThreadLocal 的使用場景

ThreadLocal 的使用場景爲:用來解決數據庫連線、Session
管理等

7.1ThreadLocal 在數據庫連線上的應用

當你在一個類中使用 static 成員變數時,一定要問自己這
個 static 成員變數需要考慮「執行緒安全嗎?」(也就是說多個
執行緒需要自己獨立的 static 成員變數嗎?)如果需要那就需
要使用 ThreadLocal。

threadLocalProject_2

Dao.java

package com.bjsxt.dbutil;

import java.sql.Connection;

public class Dao {
	public void insert(){
		//獲取連線
		//System.out.println("Dao.insert()"+Thread.currentThread().getName()+DBUtil.getConnection());
		//Connection conn=new DBUtil2().getConnection();
		Connection conn=DBUtil3.getConnection();
		System.out.println("Dao.insert()"+Thread.currentThread().getName()+conn);
		
	}
	public void delete(){
		//獲取連線
		//System.out.println("Dao.delete()"+Thread.currentThread().getName()+DBUtil.getConnection());
		//Connection conn=new DBUtil2().getConnection();
		Connection conn=DBUtil3.getConnection();
		System.out.println("Dao.delete()"+Thread.currentThread().getName()+conn);
	}
	public void update(){
		//獲取連線
		//System.out.println("Dao.update()"+Thread.currentThread().getName()+DBUtil.getConnection());
		//Connection conn=new DBUtil2().getConnection();
		Connection conn=DBUtil3.getConnection();
		System.out.println("Dao.update()"+Thread.currentThread().getName()+conn);
	}
	public void select(){
		//獲取連線
		//System.out.println("Dao.select()"+Thread.currentThread().getName()+DBUtil.getConnection());
		//Connection conn=new DBUtil2().getConnection();
		Connection conn=DBUtil3.getConnection();
		System.out.println("Dao.select()"+Thread.currentThread().getName()+conn);
	}
}

DBUtil.java

package com.bjsxt.dbutil;



import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
	private static final String DRIVER="com.mysql.jdbc.Driver";
	private static final String	USER="root";
	private static final String PWD="root";
	private static final String URL="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";
	
	//定義一個數據庫連線
	private static Connection conn=null;
	//獲取連線
	public   static Connection getConnection(){
		
		try {
			Class.forName(DRIVER);
			
			if(conn==null){
				
				conn=DriverManager.getConnection(URL, USER, PWD);
			}
			
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		return conn;
	}
	//關閉連線的方法
	public   static void colseConnection(){
		if (conn!=null) {
			try {
				conn.close();
			
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
	public static void main(String[] args) {
		System.out.println(getConnection());
	}
}

DBUtil2.java

package com.bjsxt.dbutil;



import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil2 {
	private static final String DRIVER="com.mysql.jdbc.Driver";
	private static final String	USER="root";
	private static final String PWD="root";
	private static final String URL="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";
	
	//定義一個數據庫連線
	private  Connection conn=null;
	//獲取連線
	public    Connection getConnection(){
		
		try {
			Class.forName(DRIVER);
			
			if(conn==null){
				
				conn=DriverManager.getConnection(URL, USER, PWD);
			}
			
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		return conn;
	}
	//關閉連線的方法
	public    void colseConnection(){
		if (conn!=null) {
			try {
				conn.close();
			
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
	
}

DBUtil3.java

package com.bjsxt.dbutil;



import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil3 {
	private static final String DRIVER="com.mysql.jdbc.Driver";
	private static final String	USER="root";
	private static final String PWD="root";
	private static final String URL="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";
	
	//定義一個數據庫連線
	private static Connection conn=null;
	private static ThreadLocal<Connection> connContainer=new ThreadLocal<Connection>(){
		protected Connection initialValue() {
			try {
				Class.forName(DRIVER);
				
				if(conn==null){
					
					conn=DriverManager.getConnection(URL, USER, PWD);
				}
				
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			return conn;
		};
	};
	//獲取連線
	public   static Connection getConnection(){
		return connContainer.get(); //獲取連線
		
	}
	//關閉連線的方法
	public   static void colseConnection(){
		if (conn!=null) {
			try {
				conn.close();
			
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
	public static void main(String[] args) {
		System.out.println(getConnection());
	}
}

Test.java

package com.bjsxt.dbutil;

public class Test {
	public static void main(String[] args) {
		MyThread my=new MyThread();
		for(int i=0;i<3;i++){
			new Thread(my).start();
		}
	}
}
class MyThread implements Runnable{
	Dao d=new Dao();
	@Override
	public void run() {
		d.insert();
		d.update();
		d.delete();
		d.select();
		
	}
	
}

7.ThreadLocal在數據庫連線中的使用

在这里插入图片描述